Quando usar Task.Delay, quando usar Thread.Sleep?

386

Existem boas regras para quando usar Task.Delay versus Thread.Sleep ?

  • Especificamente, existe um valor mínimo para garantir que um seja eficaz / eficiente em relação ao outro?
  • Por fim, como o Task.Delay causa a alternância de contexto em uma máquina de estado assíncrona / aguardada, há uma sobrecarga de usá-la?
Tom K.
fonte
2
10ms é um monte de ciclos no mundo de computador ...
Brad Christie
Quão rápido deve ser? Quais problemas de desempenho você tem?
LB
4
Eu acho que a pergunta mais pertinente é em que contexto você pretende usar um desses? Sem essas informações, o escopo é muito amplo. O que você quer dizer com efetivo / eficiente? Você está se referindo à precisão, eficiência de energia etc.? Estou muito curioso para saber em que contexto isso importa.
James World
4
O mínimo é 15.625 ms, valores menores que a taxa de interrupção do relógio não têm efeito. Task.Delay sempre queima um System.Threading.Timer, Sleep não tem sobrecarga. Você não se preocupa com sobrecarga quando escreve um código que não faz nada.
21713 Hans Passant
algo que eu não vi mencionado, mas acho importante, seria que o Task.Delay suporta um CancellationToken, o que significa que você pode interromper o atraso, se, por exemplo, estiver usando-o para desacelerar um processo de ciclo. isso também significa que seu processo pode responder rapidamente quando você deseja cancelá-lo. mas você pode conseguir o mesmo com o Thread.Sleep, diminuindo o intervalo do ciclo de sono e verifique o manual do token.
DROA

Respostas:

370

Use Thread.Sleepquando quiser bloquear o encadeamento atual.

Use Task.Delayquando desejar um atraso lógico sem bloquear o encadeamento atual.

A eficiência não deve ser uma preocupação primordial com esses métodos. Seu uso primário no mundo real é como temporizadores de repetição para operações de E / S, que são da ordem de segundos e não de milissegundos.

Stephen Cleary
fonte
3
É o mesmo caso de uso principal: um temporizador de repetição.
Stephen Cleary
4
Ou quando você não deseja mastigar a CPU em um loop principal.
Eddie Parker
5
@RoyiNamir: Não. Não há "outro tópico". Internamente, é implementado com um timer.
Stephen Cleary
20
A sugestão de não se preocupar com eficiência é imprudente. Thread.Sleepirá bloquear o thread atual que causa uma troca de contexto. Se você estiver usando um pool de threads, isso também poderá fazer com que um novo thread seja alocado. As duas operações são bastante pesadas, enquanto as multitarefas cooperativas fornecidas pelo Task.Delayetc são projetadas para evitar toda essa sobrecarga, maximizar a produtividade, permitir o cancelamento e fornecer um código mais limpo.
Corillian
2
@LucaCremry onesi: I would use Thread.Sleep` para esperar dentro de um método síncrono. No entanto, nunca faço isso no código de produção; na minha experiência, tudo o Thread.Sleepque eu já vi foi indicativo de algum tipo de problema de design que precisa ser corrigido corretamente.
Stephen Cleary
243

A maior diferença entre Task.Delaye Thread.Sleepé a Task.Delayintenção de executar de forma assíncrona. Não faz sentido usar Task.Delayno código síncrono. É uma péssima idéia usar Thread.Sleepno código assíncrono.

Normalmente você ligará Task.Delay() com a awaitpalavra-chave:

await Task.Delay(5000);

ou, se você deseja executar algum código antes do atraso:

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

Adivinha o que isso vai imprimir? Em execução por 0,0070048 segundos. Se movermos o await delayacima Console.WriteLine, em vez disso, ele imprimirá Running por 5.0020168 segundos.

Vejamos a diferença com 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");
    }
}

Tente prever o que isso imprimirá ...

assíncrono: Iniciando
assíncrono: executando por 0,0070048 segundos de
sincronização: Iniciando
assíncrono: executando por 5,0119008 segundos
assíncrono: concluído
sincronizado: executando por 5,0020168 segundos
sincronizado: concluído

Além disso, é interessante notar que Thread.Sleepé muito mais preciso, a precisão da ms não é realmente um problema, enquanto Task.Delaypode demorar 15 a 30 ms no mínimo. A sobrecarga em ambas as funções é mínima em comparação com a precisão de ms que elas possuem (use StopwatchClasse se precisar de algo mais preciso). Thread.Sleepainda amarra seu thread, Task.Delaysolte-o para fazer outro trabalho enquanto espera.

Dorus
fonte
15
Por que "é uma péssima idéia usar o Thread.Sleep no código assíncrono"?
sunside
69
@sunside Uma das principais vantagens do código assíncrono é permitir que um thread trabalhe em várias tarefas ao mesmo tempo, evitando o bloqueio de chamadas. Isso evita a necessidade de grandes quantidades de encadeamentos individuais e permite que um conjunto de encadeamentos atenda a muitas solicitações ao mesmo tempo. No entanto, como o código assíncrono geralmente é executado no pool de threads, o bloqueio desnecessário de um único thread Thread.Sleep()consome um thread inteiro que, de outra forma, poderia ser usado em outro lugar. Se muitas tarefas forem executadas com o Thread.Sleep (), há uma grande chance de esgotar todos os threads do conjunto de threads e prejudicar seriamente o desempenho.
Ryan
11
Pegue. Eu estava sentindo falta da noção de código assíncrono no sentido de asyncmétodos, pois eles são encorajados a serem usados. Basicamente, é apenas uma má ideia executar Thread.Sleep()em um encadeamento de threads, não uma má ideia em geral. Afinal, há TaskCreationOptions.LongRunningquando seguir a Task.Factory.StartNew()rota (embora desanimada) .
sunside
6
parabéns paraawait wait
Eric Wu
2
@Reyhn A documentação sobre isso é que Tasl.Delayusa o timer do sistema. Como "O relógio do sistema" marca "a uma taxa constante.", A velocidade do timer do sistema é de cerca de 16 ms, qualquer atraso solicitado será arredondado para um número de ticks do relógio do sistema, compensado pelo tempo até o primeiro Carraça. Consulte a documentação do Task.Delay msdn em docs.microsoft.com/en-us/dotnet/api/… e role para baixo para ver os comentários.
Dorus
28

se o encadeamento atual for eliminado e você o usar Thread.Sleepe estiver em execução, poderá obter um ThreadAbortException. Com Task.Delayvocê, você sempre pode fornecer um token de cancelamento e matá-lo normalmente. Essa é uma razão que eu escolheria Task.Delay. consulte http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx

Também concordo que a eficiência não é fundamental neste caso.

Balanikas
fonte
2
Suponha que temos a seguinte situação: await Task.Delay(5000). Quando eu mato a tarefa, recebo TaskCanceledException(e a suprimo), mas minha discussão ainda está viva. Arrumado! :)
AlexMelw
24

Eu quero adicionar algo. Na verdade, Task.Delayé um mecanismo de espera baseado em timer. Se você olhar para a fonte , encontrará uma referência a uma Timerclasse responsável pelo atraso. Por outro lado, Thread.Sleepna verdade, o encadeamento atual é interrompido, dessa maneira você está apenas bloqueando e desperdiçando um encadeamento. No modelo de programação assíncrona, você sempre deve usar Task.Delay()se desejar que algo (continuação) aconteça após algum atraso.

criptografado
fonte
'waitit Task.Delay ()' libera o thread para fazer outras coisas até que o cronômetro expire, 100% limpo. Mas e se eu não puder usar 'wait', pois o método não é prefixado com 'async'? Só posso chamar 'Task.Delay ()'. Nesse caso, o thread ainda está bloqueado, mas tenho a vantagem de cancelar o Delay () . Isso está correto?
Erik Stroeken
5
@ErikStroeken Você pode passar tokens de cancelamento para o thread e a tarefa. Task.Delay (). Wait () será bloqueado, enquanto Task.Delay () apenas cria a tarefa se usada sem aguardar. O que você faz com essa tarefa é com você, mas o encadeamento continua.