Когда использовать Task.Delay, когда использовать Thread.Sleep?

386

Есть хорошие правила для использования Task.Delay против Thread.Sleep ?

  • В частности, существует ли минимальное значение для обеспечения того, чтобы один был эффективным / действенным по сравнению с другим?
  • Наконец, поскольку Task.Delay вызывает переключение контекста на автомате асинхронного ожидания / ожидания, есть ли издержки его использования?
Том К.
источник
2
10 мс - много циклов в компьютерном мире ...
Брэд Кристи,
Как быстро это должно быть? Какие проблемы с производительностью у вас есть?
LB
4
Я думаю, что более уместный вопрос в том, в каком контексте вы собираетесь использовать любой из них? Без этой информации сфера слишком широка. Что вы подразумеваете под эффективным / действенным? Вы имеете в виду точность, энергоэффективность и т. Д.? Мне очень любопытно узнать, в каком контексте это имеет значение.
Джеймс Уорлд
4
Минимальное значение составляет 15,625 мсек, значения, меньшие частоты прерывания тактовой частоты, не действуют. Task.Delay всегда сжигает System.Threading.Timer, Sleep не имеет накладных расходов. Вы не беспокоитесь о накладных расходах, когда пишете код, который ничего не делает.
Ганс Пассант
то, о чем я не упоминал, но я думаю, что это важно, - это то, что Task.Delay поддерживает CancellationToken, то есть вы можете прервать задержку, если, например, используете ее для замедления цикла. это также означает, что ваш процесс может быстро реагировать, если вы хотите отменить его. но вы можете добиться того же с помощью Thread.Sleep, сократив интервал цикла ожидания, и проверьте ручной режим Token.
Дроа

Ответы:

370

Используйте, Thread.Sleepкогда вы хотите заблокировать текущий поток.

Используйте, Task.Delayкогда вы хотите, чтобы логическая задержка не блокировала текущий поток.

Эффективность не должна быть первостепенной задачей при использовании этих методов. Их основное реальное использование - таймеры повторов для операций ввода / вывода, которые имеют порядок секунд, а не миллисекунд.

Стивен Клири
источник
3
Это тот же основной случай использования: таймер повтора.
Стивен Клири
4
Или когда вы не хотите жевать процессор в основном цикле.
Эдди Паркер
5
@RoyiNamir: Нет. Нет "другой темы". Внутренне это реализовано с помощью таймера.
Стивен Клири
20
Предложение не беспокоиться об эффективности опрометчиво. Thread.Sleepзаблокирует текущий поток, который вызывает переключение контекста. Если вы используете пул потоков, это также может привести к выделению нового потока. Обе операции довольно тяжелые, в то время как совместная многозадачность, предоставляемая Task.Delayetc, предназначена для того, чтобы избежать всех этих издержек, максимизировать пропускную способность, разрешить отмену и обеспечить более чистый код.
Кориллиан
2
@LucaCremry onesi: I would use Thread.Sleep` для ожидания внутри синхронного метода. Тем не менее, я никогда не делаю этого в производственном коде; по моему опыту, каждое, что Thread.Sleepя когда-либо видел, было показателем какой-то проблемы дизайна, которая должна быть исправлена ​​должным образом.
Стивен Клири
243

Самое большое различие между Task.Delayи Thread.Sleepзаключается в том, что Task.Delayон предназначен для асинхронной работы. Не имеет смысла использовать Task.Delayв синхронном коде. Это ОЧЕНЬ плохая идея для использования Thread.Sleepв асинхронном коде.

Обычно вы будете звонить Task.Delay() с awaitключевым словом:

await Task.Delay(5000);

или, если вы хотите запустить некоторый код до задержки:

var sw = new Stopwatch();
sw.Start();
Task delay = Task.Delay(5000);
Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
await delay;

Угадайте, что это будет печатать? Запуск за 0,0070048 секунд. Если вместо этого переместить await delayвышеуказанное Console.WriteLine, будет напечатано «Запуск в течение 5.0020168 секунд».

Давайте посмотрим на разницу с Thread.Sleep:

class Program
{
    static void Main(string[] args)
    {
        Task delay = asyncTask();
        syncCode();
        delay.Wait();
        Console.ReadLine();
    }

    static async Task asyncTask()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("async: Starting");
        Task delay = Task.Delay(5000);
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        await delay;
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("async: Done");
    }

    static void syncCode()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("sync: Starting");
        Thread.Sleep(5000);
        Console.WriteLine("sync: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("sync: Done");
    }
}

Попробуйте предсказать, что это будет печатать ...

асинхронный: запуск
асинхронного: запуск в течение 0,0070048 секунд
синхронизация: запуск
асинхронного: запуск в течение 5,0119008 секунд
асинхронный запуск :
синхронизация: запуск в течение 5,0020168 секунд
синхронизация: выполнено

Кроме того, интересно отметить, что Thread.Sleepэто гораздо более точно, точность мс не является проблемой, хотя Task.Delayможет занять минимум 15-30 мс. Затраты на обе функции минимальны по сравнению с точностью до мс, которую они имеют (используйте StopwatchClass, если вам нужно что-то более точное). Thread.Sleepвсе еще связывает свою Нить, Task.Delayотпустите ее для выполнения другой работы, пока вы ждете.

Dorus
источник
15
Почему «ОЧЕНЬ плохая идея использовать Thread.Sleep в асинхронном коде»?
Sunside
69
@sunside Одним из основных преимуществ асинхронного кода является то, что один поток может работать одновременно с несколькими задачами, избегая блокирования вызовов. Это устраняет необходимость в огромном количестве отдельных потоков и позволяет пулу потоков обслуживать много запросов одновременно. Однако, учитывая, что асинхронный код обычно выполняется в пуле потоков, ненужная блокировка отдельного потока Thread.Sleep()потребляет весь поток, который в противном случае мог бы использоваться в другом месте. Если многие задачи выполняются с Thread.Sleep (), существует высокая вероятность исчерпания всех потоков пула потоков и серьезного снижения производительности.
Райан
1
Возьми. Мне не хватало понятия асинхронного кода в смысле asyncметодов, так как их рекомендуется использовать. Это просто плохая идея запускать Thread.Sleep()в потоке потоков, а не плохая идея в целом. В конце концов, есть TaskCreationOptions.LongRunningпуть (хотя и обескураженный) Task.Factory.StartNew().
Солнце
6
слава дляawait wait
Эрик Ву
2
@Reyhn Документация по этому вопросу Tasl.Delayиспользует системный таймер. Поскольку «Системные часы» отсчитывают «с постоянной скоростью», скорость такта системного таймера составляет около 16 мс, любая запрошенная вами задержка будет округлена до количества тактов системных часов, смещенных на время до первого поставить галочку. См. Документацию Task.Delay msdn по адресу docs.microsoft.com/en-us/dotnet/api/… и прокрутите вниз до примечаний.
Дорус
28

если текущий поток уничтожен, и вы используете Thread.Sleepего, и он выполняется, то вы можете получить ThreadAbortException. При этом Task.Delayвы всегда можете предоставить токен отмены и изящно убить его. Это одна из причин, я бы выбрал Task.Delay. см. http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx

Я также согласен с тем, что в данном случае эффективность не имеет первостепенного значения.

Balanikas
источник
2
Предположим , мы получили следующую ситуацию: await Task.Delay(5000). Когда я убиваю задачу, я получаю TaskCanceledException(и подавляю ее), но моя нить все еще жива. Ухоженная! :)
AlexMelw
24

Я хочу кое-что добавить. На самом деле, Task.Delayэто механизм ожидания на основе таймера. Если вы посмотрите на источник, вы найдете ссылку на Timerкласс, который отвечает за задержку. С другой стороны, Thread.Sleepфактически заставляет текущий поток спать, таким образом, вы просто блокируете и тратите один поток. В модели асинхронного программирования вы всегда должны использовать, Task.Delay()если хотите, чтобы что-то (продолжение) произошло после некоторой задержки.

зашифрованный
источник
'await Task.Delay ()' освобождает поток, чтобы делать другие вещи, пока не истечет таймер, 100% очистки. Но что, если я не могу использовать «await», так как метод не имеет префикса «async»? Тогда я могу только вызвать Task.Delay (). В этом случае поток все еще заблокирован, но у меня есть преимущество отмены задержки () . Это верно?
Эрик Струкен
5
@ErikStroeken Вы можете передавать токены отмены как в поток, так и в задачу. Task.Delay (). Wait () будет блокироваться, в то время как Task.Delay () просто создает задачу, если используется без await. То, что вы делаете с этой задачей, зависит от вас, но процесс продолжается.