Насколько масштабируемым является System.Threading.Timer?

Я пишу приложение, которое должно будет использовать Timer, но потенциально очень много из них. Насколько масштабируемым является класс System.Threading.Timer? В документации просто сказано, что это «легкий», но не поясняется. Эти таймеры засасываются в один поток (или очень маленький пул потоков), который обрабатывает все обратные вызовы от имени Timer, или у каждого Timer есть свой собственный поток?

Думаю, еще один способ перефразировать вопрос: как реализован System.Threading.Timer?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
26
0
9 945
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Я думаю, вы можете переосмыслить свой дизайн (то есть, если вы сами контролируете дизайн). Если вы используете так много таймеров, что это действительно беспокоит вас, очевидно, что есть некоторый потенциал для консолидации.

Вот хорошая статья из журнала MSDN Magazine несколько лет назад, в которой сравниваются три доступных класса таймеров и дается некоторое представление об их реализациях:

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx

Ответ принят как подходящий

Я говорю это в ответ на множество вопросов: не забывайте, что (управляемый) исходный код фреймворка доступен. Вы можете использовать этот инструмент, чтобы получить все это: http://www.codeplex.com/NetMassDownloader

К сожалению, в этом конкретном случае большая часть реализации находится в машинном коде, поэтому вы не можете на это смотреть ...

Однако они определенно используют потоки пула, а не таймер потока.

Стандартный способ реализовать большую коллекцию таймеров (именно так ядро ​​делает это изнутри, и я подозреваю, что косвенно так и заканчивается ваша большая коллекция таймеров) состоит в том, чтобы поддерживать список, отсортированный по времени до истечения срока действия, поэтому система всегда должна беспокоиться только о проверке следующего таймера, срок действия которого истекает, а не всего списка.

Грубо говоря, это дает O (log n) для запуска таймера и O (1) для обработки запущенных таймеров.

Обновлено: только что искал в книге Джеффа Рихтера. Он говорит (о Threading.Timer), что он использует один поток для всех объектов Timer, этот поток знает, когда должен наступить следующий таймер (т.е. как указано выше), и вызывает ThreadPool.QueueUserWorkItem для обратных вызовов при необходимости. Это приводит к тому, что если вы не завершите обслуживание одного обратного вызова на таймере до истечения следующего срока, ваш обратный вызов повторно войдет в другой поток пула. Подводя итог, я сомневаюсь, что вы столкнетесь с большой проблемой при наличии большого количества таймеров, но вы можете столкнуться с исчерпанием пула потоков, если большое их количество запускается на одном таймере и / или их обратные вызовы работают медленно.

Очередь приоритетов, вероятно, будет более эффективной, чем отсортированный список, если все таймеры не будут добавлены сразу в начале, затем отсортированы и больше не будут добавлены позже.

RAL 25.02.2010 07:39

Конечно - «список, отсортированный по времени до истечения срока», безусловно, может быть своего рода приоритетной очередью - я не имел в виду «список, в котором была выполнена операция сортировки».

Will Dean 26.02.2010 12:23

Я просто потратил некоторое время на просмотр кода в sscli. Обратите внимание, что .NET ThreadPool сильно изменился с момента выпуска Rotor, поэтому вполне возможно, что System.Threading.Timer также сильно изменился. На самом деле таймеры были сильно нарушены в .NET 1.1 и исправлены только для обеспечения безопасности и исключений в .NET 2.0. В любом случае, в Rotor таймеры хранятся в связанном списке и запускаются выделенным потоком запуска таймера. Существует один поток запуска таймера для всей среды выполнения (даже для нескольких доменов приложений).

Michael Graczyk 29.06.2012 12:58

Поток просто просматривает список таймеров и вызывает (собственный) QueueUserWorkItem для всех таймеров, срок действия которых истек. Операции вставки новых таймеров и удаления старых выполняются как вызовы APC в потоке таймера (msdn.microsoft.com/en-us/library/windows/desktop/…). Таким образом, реализация спроектирована так, чтобы быть простой и безопасной (сейчас), но не очень производительной. Тем не менее, это должно быть достаточно быстро для большинства ситуаций, но если вы пытаетесь создать 10000 таймеров в секунду, у вас, вероятно, возникнут проблемы.

Michael Graczyk 29.06.2012 13:02

Кстати, функция срабатывания таймера - это строка 3864 в пуле потоков win32.C++, если вам интересно.

Michael Graczyk 29.06.2012 13:06

На самом деле после небольшого теста выяснилось, что обход таймера по-прежнему O (log n), но даже все же мне удалось заставить таймеры идти в ногу со своей машиной со скоростью ~ 20000 в секунду.

Michael Graczyk 29.06.2012 13:17

^^ как говорит Дэнни Смурф: Объедините их. Создайте службу таймера и спросите у таймеров. Потребуется только сохранить 1 активный таймер (для следующего вызова) и историю всех запросов таймера и пересчитать это в AddTimer () / RemoveTimer ().

Consolidate them. Create a timer service and ask that for the timers. It will only need to keep 1 active timer (for the next due call)...

Чтобы это было улучшением по сравнению с простым созданием большого количества объектов Threading.Timer, вы должны предположить, что это не совсем то, что Threading.Timer уже делает внутри. Мне было бы интересно узнать, как вы пришли к такому выводу (я не разбирал нативные части фреймворка, так что вы вполне можете быть правы).

Другие вопросы по теме