Коли використовувати Task.Delay, коли використовувати Thread.Sleep?


385

Чи є хороші правила щодо використання Task.Delay проти Thread.Sleep ?

  • Зокрема, чи є мінімальне значення для забезпечення ефективності / ефективності одного з іншого?
  • Нарешті, оскільки Task.Delay викликає переключення контексту на машину стану асинхронізування / очікування, чи є накладні витрати на його використання?

2
10 мс - це багато циклів у комп'ютерному світі ...
Бред Крісті

Як швидко це повинно бути? Які проблеми у роботі є?
ЛБ

4
Я думаю, що більш актуальним є питання, в якому контексті ви маєте намір використати будь-яке з них? Без цієї інформації сфера застосування занадто широка. Що ви маєте на увазі під ефективним / дієвим? Ви маєте на увазі точність, енергоефективність тощо? Мені дуже цікаво дізнатися, в якому контексті це має значення.
Джеймс Світ

4
Мінімум - 15,625 мсек, значення, менші за швидкість переривання тактової частоти, не впливають. Task.Delay завжди спалює System.Threading.Timer, сон не має накладних витрат. Ви не турбуєтесь про накладні витрати, коли пишете код, який нічого не робить.
Ганс Пасант

щось, що я не бачив згаданого, але я вважаю важливим, це те, що Task.Delay підтримує CancellationToken, тобто ви можете перервати затримку, якщо ви, наприклад, використовуєте це для уповільнення циклу. це також означає, що ваш процес може швидко реагувати, коли ви хочете його скасувати. але ви можете домогтися того ж з Thread.Sleep, скоротивши інтервал циклу сну коротше, і перевірте ручну токену Token.
Дроя

Відповіді:


369

Використовуйте, Thread.Sleepколи ви хочете заблокувати поточну нитку.

Використовуйте, Task.Delayколи потрібно логічну затримку, не блокуючи поточну нитку.

Ефективність не повинна мати першочергового значення для цих методів. Їх основне використання в реальному світі - це таймери для повторних операцій для операцій вводу / виводу, які є на порядок секунд, а не мілісекунд.


3
Це той самий основний випадок використання: таймер повторного використання.
Стівен Клірі

4
Або коли ви не хочете жувати процесор у головному циклі.
Едді Паркер

5
@RoyiNamir: Ні. "Іншої теми" немає. Внутрішньо вона реалізована за допомогою таймера.
Стівен Клірі

20
Пропозиція не турбуватися про ефективність є необдуманою. Thread.Sleepзаблокує поточний потік, що викликає контекстний комутатор. Якщо ви використовуєте пул потоків, це також може призвести до виділення нового потоку. Обидві операції є досить важкими, тоді як спільна багатозадачність, що надається Task.Delayтощо, розроблена таким чином, щоб уникнути всіх накладних витрат, максимізувати пропускну здатність, дозволити скасування та надання більш чистого коду.
Corillian

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");
    }
}

Спробуйте передбачити, що це буде надрукувати ...

async: запуск
асинхронізації: запуск
синхронізації 0,0070048 секунд : запуск
асинхронізації: запуск 5,0119008 секунд
асинхронізація:
синхронізація завершена : запуск 5,0020168 секунд
синхронізація: завершено

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


15
Чому "ДУЖЕ погана ідея використовувати Thread.Sleep в асинхронному коді"?
сонячний день

69
@sunside Однією з головних переваг асинхронного коду є те, щоб дозволити одному потоку працювати над декількома завданнями одночасно, уникаючи блокування дзвінків. Це дозволяє уникнути потреби у величезній кількості окремих потоків і дозволяє потоковому сервісу обслуговувати багато запитів одночасно. Однак, враховуючи, що код асинхронізації, як правило, працює на потоці ниток, без необхідності блокування однієї нитки Thread.Sleep()споживає цілу нитку, яка в іншому випадку може бути використана в іншому місці. Якщо багато завдань виконуються з Thread.Sleep (), велика ймовірність вичерпати всі потоки ниток і серйозно завадити роботі.
Райан

1
Дістань. Мені не вистачало поняття асинхронного коду в сенсі asyncметодів, оскільки їх рекомендують використовувати. Це, в основному, лише погана ідея запускати Thread.Sleep()в нитку нитки, а не погана ідея взагалі. Зрештою, є, TaskCreationOptions.LongRunningколи їдеш (хоч і не заважаєш) Task.Factory.StartNew()маршрутом.
сонця

6
kudos forawait 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

Я також погоджуюся, що ефективність не є першорядною у цьому випадку.


2
Припустимо , ми отримали наступну ситуацію: await Task.Delay(5000). Коли я вбиваю завдання, я отримую TaskCanceledException(і придушую його), але моя нитка все ще жива. Акуратно! :)
AlexMelw

24

Я хочу щось додати. Насправді Task.Delayце механізм очікування, заснований на таймері. Якщо ви подивитесь на джерело, ви знайдете посилання на Timerклас, який відповідає за затримку. З іншого боку, Thread.Sleepфактично змушує поточну нитку спати, таким чином ви просто блокуєте і витрачаєте одну нитку. У моделі програмування async завжди слід використовувати, Task.Delay()якщо ви хочете, щоб щось (продовження) відбулося після деякої затримки.


'очікувати Task.Delay ()' звільняє потік робити інші речі, поки не закінчиться таймер, на 100% ясно. Але що робити, якщо я не можу використовувати "wait", оскільки метод не має префікса "async"? Тоді я можу лише зателефонувати "Task.Delay ()". У цьому випадку потік все ще заблокований, але я маю перевагу скасувати затримку () . Це правильно?
Ерік Стройкен

5
@ErikStroeken Ви можете передавати маркери скасування як на потік, так і на завдання. Task.Delay (). Wait () блокується, тоді як Task.Delay () просто створює завдання, якщо його використовувати без очікування. Що ви робите з цим завданням, залежить від вас, але нитка продовжується.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.