Является ли Task.Delay действительно асинхронным, как операция ввода-вывода, т. е. полагается ли он на аппаратное обеспечение и прерывания вместо потока?

Я нашел массу связанного контента, который все ходил вокруг да около, и я так и не смог найти ответ. Я почти на 100% уверен, что Task.Delay(int) не использует поток, потому что я могу запустить этот код на своей машине только с 16 логическими процессорами:

var tasks = new List<Task>();
for(int i = 1; i < 100000; i++) tasks.Add(Task.Delay(10000));
await Task.WhenAll(tasks);

И это занимает десять секунд, чтобы закончить. Я думаю, что если бы он использовал примерно сто тысяч потоков, это заняло бы немного больше времени.

Итак, мой вопрос: как работает Task.Delay(int)? Не так, как указывает этот плохо озаглавленный вопрос SO, а с точки зрения многопоточности и аппаратных ресурсов.

Вы можете увидеть код здесь: github.com/dotnet/runtime/blob/master/src/libraries/… - он основан на обратных вызовах таймера

Marc Gravell 09.12.2020 16:23

Спасибо, Марк. Думаю, я все еще упускаю суть... так что таймеры не используют потоки? Я предполагаю, что суть моего вопроса будет заключаться в следующем: как это реализовано, чтобы таймер не блокировал поток даже с обратным вызовом? Между запуском таймера и обратным вызовом должно быть что-то среднее.

rory.ap 09.12.2020 16:25

ОС помогает с такими вещами, как таймеры

Marc Gravell 09.12.2020 16:26

Ладно, мы подъезжаем. Так как? ОС по-прежнему должна использовать потоки.

rory.ap 09.12.2020 16:27

Таймер .NET использует один поток, который выполняет все ожидания. Когда вы регистрируете новый таймер, таймер вычисляет, какой таймер истекает следующим, ждет это количество времени и запускает задачи tpl, чтобы сигнализировать о завершении, чтобы отправить готовые к запуску обратные вызовы таймера. Когда вы регистрируете 10K таймеров, то только один поток ожидает их всех, так как близлежащие таймеры собираются в одно и то же ожидание, насколько я помню. Затем 10 000 задач с вашими обратными вызовами с истекающим сроком действия вызываются через пул потоков.

Alois Kraus 09.12.2020 16:43

«ОС по-прежнему должна использовать потоки» — для окон см. WM_TIMER (winapi). Я понятия не имею, как и кем отправляются сообщения Windows, вы можете узнать больше о winapi, если вам нужно.

Sinatr 09.12.2020 16:47

@AloisKraus, это просто неправильно, нет потоков (ну может очень глубоко там мало потоков, обслуживающих аппаратное обеспечение таймера, это я не знаю), ничего не вычисляется, как вы объяснили, смотрите сами.

Sinatr 09.12.2020 16:50

@Sinatr: вам следует взглянуть на исходный код, на который ссылается Марк. .NET System.Threading.Timers не использует WM_TIMERS, поскольку для этого потребуется дескриптор окна и поток перекачки сообщений. Есть и другие, но это не вопрос. Я не говорил, что потоков нет, но он использует один поток для проверки следующих таймеров с истекшим сроком действия. Если я считаю правильно 1 != 0.

Alois Kraus 10.12.2020 07:11
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
8
293
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

В текущей реализации .NET имеется единственный «поток таймера», который просто отслеживает экземпляры управляемого таймера и вызывает их события в соответствующее время. Этот поток таймера заблокирует своего управляющего сигнала с тайм-аутом, установленным на время выполнения следующего таймера. Управляющий сигнал используется для добавления/удаления/изменения таймеров, поэтому, когда этот блокирующий запрос истекает, поток таймера знает, что сработал следующий таймер. Это обычная операция блокировки потока, поэтому внутренне поток простаивает и удаляется из очереди планировщика до тех пор, пока эта операция блокировки не завершится или не истечет время ожидания. Время ожидания этих операций обрабатывается прерыванием таймера планировщика ОС.

Так что технически поток есть, но только один поток на процесс, а не один поток на Task.Delay.

Еще раз подчеркну, что это в текущей реализации .NET. Были предложены и другие решения, такие как один поток таймера на ЦП или динамический пул потоков таймера. Возможно, они были опробованы и по какой-то причине отвергнуты, а возможно, в будущем будет принято альтернативное решение. Насколько я знаю, это нигде официально не задокументировано, так что это деталь реализации.

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