Я нашел массу связанного контента, который все ходил вокруг да около, и я так и не смог найти ответ. Я почти на 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, а с точки зрения многопоточности и аппаратных ресурсов.
Спасибо, Марк. Думаю, я все еще упускаю суть... так что таймеры не используют потоки? Я предполагаю, что суть моего вопроса будет заключаться в следующем: как это реализовано, чтобы таймер не блокировал поток даже с обратным вызовом? Между запуском таймера и обратным вызовом должно быть что-то среднее.
ОС помогает с такими вещами, как таймеры
Ладно, мы подъезжаем. Так как? ОС по-прежнему должна использовать потоки.
Таймер .NET использует один поток, который выполняет все ожидания. Когда вы регистрируете новый таймер, таймер вычисляет, какой таймер истекает следующим, ждет это количество времени и запускает задачи tpl, чтобы сигнализировать о завершении, чтобы отправить готовые к запуску обратные вызовы таймера. Когда вы регистрируете 10K таймеров, то только один поток ожидает их всех, так как близлежащие таймеры собираются в одно и то же ожидание, насколько я помню. Затем 10 000 задач с вашими обратными вызовами с истекающим сроком действия вызываются через пул потоков.
«ОС по-прежнему должна использовать потоки» — для окон см. WM_TIMER (winapi). Я понятия не имею, как и кем отправляются сообщения Windows, вы можете узнать больше о winapi, если вам нужно.
@AloisKraus, это просто неправильно, нет потоков (ну может очень глубоко там мало потоков, обслуживающих аппаратное обеспечение таймера, это я не знаю), ничего не вычисляется, как вы объяснили, смотрите сами.
@Sinatr: вам следует взглянуть на исходный код, на который ссылается Марк. .NET System.Threading.Timers не использует WM_TIMERS, поскольку для этого потребуется дескриптор окна и поток перекачки сообщений. Есть и другие, но это не вопрос. Я не говорил, что потоков нет, но он использует один поток для проверки следующих таймеров с истекшим сроком действия. Если я считаю правильно 1 != 0.
В текущей реализации .NET имеется единственный «поток таймера», который просто отслеживает экземпляры управляемого таймера и вызывает их события в соответствующее время. Этот поток таймера заблокирует своего управляющего сигнала с тайм-аутом, установленным на время выполнения следующего таймера. Управляющий сигнал используется для добавления/удаления/изменения таймеров, поэтому, когда этот блокирующий запрос истекает, поток таймера знает, что сработал следующий таймер. Это обычная операция блокировки потока, поэтому внутренне поток простаивает и удаляется из очереди планировщика до тех пор, пока эта операция блокировки не завершится или не истечет время ожидания. Время ожидания этих операций обрабатывается прерыванием таймера планировщика ОС.
Так что технически поток есть, но только один поток на процесс, а не один поток на Task.Delay
.
Еще раз подчеркну, что это в текущей реализации .NET. Были предложены и другие решения, такие как один поток таймера на ЦП или динамический пул потоков таймера. Возможно, они были опробованы и по какой-то причине отвергнуты, а возможно, в будущем будет принято альтернативное решение. Насколько я знаю, это нигде официально не задокументировано, так что это деталь реализации.
Вы можете увидеть код здесь: github.com/dotnet/runtime/blob/master/src/libraries/… - он основан на обратных вызовах таймера