Kiedy używać Task.Delay, kiedy używać Thread.Sleep?

385

Czy istnieją dobre reguły, kiedy należy używać Task.Delay kontra Thread.Sleep ?

  • W szczególności, czy istnieje minimalna wartość zapewniająca, aby jedna była skuteczna / wydajna w stosunku do drugiej?
  • Wreszcie, skoro Task.Delay powoduje przełączanie kontekstu na maszynie stanu asynchronicznie / oczekuj, czy jest narzut związany z jego używaniem?
Tom K.
źródło
2
10ms to dużo cykli w świecie komputerów ...
Brad Christie,
Jak szybko to powinno być? Jakie masz problemy z wydajnością?
LB
4
Myślę, że bardziej trafnym pytaniem jest, w jakim kontekście zamierzasz użyć któregoś z nich? Bez tych informacji zakres jest zbyt szeroki. Co rozumiesz przez efektywny / wydajny? Masz na myśli dokładność, sprawność energetyczną itp.? Jestem bardzo ciekawy, w jakim kontekście to ma znaczenie.
James World
4
Minimalna wartość to 15,625 ms, wartości mniejsze niż częstotliwość przerwania zegara nie mają wpływu. Task.Delay zawsze spala System.Threading.Timer, Sleep nie ma narzutu. Podczas pisania kodu, który nic nie robi, nie musisz się martwić o koszty ogólne.
Hans Passant,
czymś, o czym nie widziałem, ale myślę, że jest ważne, byłoby to, że Task.Delay obsługuje CancellationToken, co oznacza, że ​​możesz przerwać opóźnienie, jeśli na przykład używasz go do spowolnienia procesu cyklu. oznacza to również, że proces może szybko odpowiedzieć, gdy chcesz go anulować. ale możesz osiągnąć to samo dzięki Thread.Sleep, skracając interwał snu i sprawdź manualną grę Token.
Droa,

Odpowiedzi:

369

Użyj, Thread.Sleepgdy chcesz zablokować bieżący wątek.

Użyj, Task.Delaygdy chcesz logicznego opóźnienia bez blokowania bieżącego wątku.

Efektywność nie powinna być najważniejszym problemem tych metod. Ich głównym zastosowaniem w świecie rzeczywistym są liczniki czasu ponawiania operacji we / wy, które są rzędu sekund, a nie milisekund.

Stephen Cleary
źródło
3
Jest to ten sam podstawowy przypadek użycia: ponów próbę.
Stephen Cleary
4
Lub gdy nie chcesz żuć procesora w głównej pętli.
Eddie Parker,
5
@RoyiNamir: Nie. Nie ma „innego wątku”. Wewnętrznie jest implementowany z zegarem.
Stephen Cleary
20
Sugestia, by nie przejmować się wydajnością, jest odradzana. Thread.Sleepzablokuje bieżący wątek, który powoduje zmianę kontekstu. Jeśli używasz puli wątków, może to również spowodować przydzielenie nowego wątku. Obie operacje są dość ciężkie, podczas gdy wielozadaniowość kooperacyjna zapewniona przez Task.Delayitp. Została zaprojektowana w celu uniknięcia całego tego narzutu, maksymalizacji przepustowości, umożliwienia anulowania i zapewnienia czystszego kodu.
Corillian
2
@LucaCremry onesi: I would use Thread.Sleep`, aby poczekać wewnątrz metody synchronicznej. Jednak nigdy nie robię tego w kodzie produkcyjnym; z mojego doświadczenia Thread.Sleepwynika , że wszystko, co kiedykolwiek widziałem, wskazuje na jakiś problem projektowy, który należy odpowiednio naprawić.
Stephen Cleary,
243

Największa różnica między Task.Delayi Thread.Sleeppolega na tym, że Task.Delayma on działać asynchronicznie. Nie ma sensu używać Task.Delayw kodzie synchronicznym. Używanie Thread.Sleepw kodzie asynchronicznym jest BARDZO złym pomysłem .

Zwykle zadzwonisz Task.Delay() ze awaitsłowem kluczowym:

await Task.Delay(5000);

lub, jeśli chcesz uruchomić jakiś kod przed opóźnieniem:

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

Zgadnij, co to wydrukuje? Działa przez 0,0070048 sekund. Jeśli zamiast tego przeniesiemy await delaypowyższe Console.WriteLine, zostanie wydrukowany Uruchomiony przez 5.0020168 sekund.

Spójrzmy na różnicę w 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");
    }
}

Spróbuj przewidzieć, co to wydrukuje ...

async: Uruchamianie
asynchronizacji: Uruchamianie przez 0,0070048 sekund
Synchronizacja: Uruchamianie
asynchronizacji: Uruchamianie 5,0119008 sekund Asynchronizacja
: Gotowe
Synchronizacja: Uruchamianie 5.0020168 sekund
Synchronizacja: Gotowe

Warto również zauważyć, że Thread.Sleepjest o wiele dokładniejszy, dokładność ms nie jest tak naprawdę problemem, a Task.Delaymoże zająć minimum 15-30 ms. Narzut na obie funkcje jest minimalny w porównaniu z dokładnością ms, jaką mają (użyj Stopwatchklasy, jeśli potrzebujesz czegoś dokładniejszego). Thread.Sleepnadal wiąże Wątek, Task.Delayzwolnij go, aby mógł wykonać inną pracę podczas oczekiwania.

Dorus
źródło
15
Dlaczego „BARDZO złym pomysłem jest używanie Thread.Sleep w kodzie asynchronicznym”?
sunside
69
@sunside Jedną z głównych zalet kodu asynchronicznego jest umożliwienie pracy jednego wątku na wielu zadaniach jednocześnie, unikając blokowania połączeń. Dzięki temu unika się potrzeby ogromnych ilości pojedynczych wątków i pozwala puli wątków obsłużyć wiele żądań jednocześnie. Jednak biorąc pod uwagę, że kod asynchroniczny zwykle działa w puli wątków, niepotrzebnie blokując pojedynczy wątek, Thread.Sleep()zużywa cały wątek, który w innym przypadku mógłby zostać użyty w innym miejscu. Jeśli wiele zadań jest uruchamianych za pomocą Thread.Sleep (), istnieje duże prawdopodobieństwo wyczerpania wszystkich wątków puli wątków i poważnego ograniczenia wydajności.
Ryan
1
Zdobyć. Brakowało mi pojęcia kodu asynchronicznego w sensie asyncmetod, ponieważ zachęca się je do użycia. Zasadniczo jest to zły pomysł, aby działać Thread.Sleep()w wątku puli wątków, a nie ogólnie zły pomysł. W końcu pojawia się droga TaskCreationOptions.LongRunning(choć zniechęcona) Task.Factory.StartNew().
sunside
6
hołd dlaawait wait
Eric Wu
2
@Reyhn Dokumentacja na ten temat Tasl.Delaywykorzystuje zegar systemowy. Ponieważ „Zegar systemowy„ tyka ”ze stałą szybkością.”, Prędkość tykania zegara systemowego wynosi około 16 ms, każde opóźnienie, o które poprosisz, zostanie zaokrąglone do pewnej liczby tyknięć zegara systemowego, z przesunięciem czasu do pierwszego kleszcz. Zobacz dokumentację msdn na Task.Delay docs.microsoft.com/en-us/dotnet/api/... i przewiń w dół do uwag.
Dorus,
28

jeśli bieżący wątek zostanie zabity, a ty użyjesz Thread.Sleepgo i się wykonuje, możesz otrzymać ThreadAbortException. Dzięki Task.Delayzawsze możesz podać token anulowania i z gracją go zabić. To jeden z powodów, dla których wybrałbym Task.Delay. patrz http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx

Zgadzam się również, że w tym przypadku wydajność nie jest najważniejsza.

Balanikas
źródło
2
Załóżmy, mamy następującą sytuację: await Task.Delay(5000). Gdy zabiję zadanie, dostaję TaskCanceledException(i je pomijam), ale mój wątek wciąż żyje. Schludny! :)
AlexMelw
24

Chcę coś dodać. W rzeczywistości Task.Delayjest mechanizm oczekiwania oparty na zegarze. Jeśli spojrzysz na źródło , znajdziesz odniesienie do Timerklasy odpowiedzialnej za opóźnienie. Z drugiej strony Thread.Sleepfaktycznie uśpia bieżący wątek, w ten sposób blokujesz i marnujesz jeden wątek. W modelu programowania asynchronicznego powinieneś zawsze używać, Task.Delay()jeśli chcesz, aby coś (kontynuacja) wydarzyło się z pewnym opóźnieniem.

zaszyfrowane
źródło
„Oczekuj na Task.Delay ()” uwalnia wątek do robienia innych rzeczy aż do wygaśnięcia timera, 100% wyczyszczenia. Ale co, jeśli nie mogę użyć polecenia „czekaj”, ponieważ metoda nie jest poprzedzona słowem „async”? Wtedy mogę tylko wywołać „Task.Delay ()”. W takim przypadku wątek jest nadal blokowany, ale mam tę zaletę, że anuluję opóźnienie () . Czy to jest poprawne?
Erik Stroeken
5
@ErikStroeken Możesz przekazać tokeny anulowania do wątku i zadania. Task.Delay (). Wait () zostanie zablokowany, a Task.Delay () po prostu utworzy zadanie, jeśli zostanie użyte bez oczekiwania. To, co zrobisz z tym zadaniem, zależy od ciebie, ale wątek trwa.