¿Cuándo usar Task.Delay, cuándo usar Thread.Sleep?

386

¿Hay buenas reglas para cuándo usar Task.Delay versus Thread.Sleep ?

  • Específicamente, ¿hay un valor mínimo para que uno sea efectivo / eficiente sobre el otro?
  • Por último, dado que Task.Delay provoca el cambio de contexto en una máquina de estado asíncrono / espera, ¿hay algún gasto adicional por usarlo?
Tom K.
fuente
2
10 ms son muchos ciclos en el mundo de la informática ...
Brad Christie
¿Qué tan rápido debería ser? ¿Qué problemas de rendimiento tienes?
LB
44
Creo que la pregunta más pertinente es ¿en qué contexto piensa utilizar alguno de estos? Sin esa información, el alcance es demasiado amplio. ¿Qué quieres decir con efectivo / eficiente? ¿Se refiere a precisión, eficiencia energética, etc.? Tengo mucha curiosidad por saber en qué contexto esto importa.
James World
44
El mínimo es de 15.625 ms, los valores inferiores a la velocidad de interrupción del reloj no tienen efecto. Task.Delay siempre quema un System.Threading.Timer, Sleep no tiene sobrecarga. No te preocupes por los gastos generales cuando escribes código que no hace nada.
Hans Passant
Algo que no vi mencionado, pero creo que es importante, sería que Task.Delay admite un CancellationToken, lo que significa que puede interrumpir el retraso, si, por ejemplo, lo está utilizando para ralentizar un proceso de ciclo. Esto también significa que su proceso puede responder rápidamente cuando desee cancelarlo. pero puede lograr lo mismo con Thread.Sleep haciendo que el intervalo del ciclo de sueño sea más corto y verifique la reproducción manual de Token.
Droa

Respuestas:

370

Úselo Thread.Sleepcuando desee bloquear el hilo actual.

Úselo Task.Delaycuando desee un retraso lógico sin bloquear el hilo actual.

La eficiencia no debe ser una preocupación primordial con estos métodos. Su uso principal en el mundo real es como temporizadores de reintento para operaciones de E / S, que son del orden de segundos en lugar de milisegundos.

Stephen Cleary
fuente
3
Es el mismo caso de uso primario: un temporizador de reintento.
Stephen Cleary
44
O cuando no quieres masticar CPU en un bucle principal.
Eddie Parker
55
@RoyiNamir: No. No hay "otro hilo". Internamente, se implementa con un temporizador.
Stephen Cleary el
20
La sugerencia de no preocuparse por la eficiencia es desaconsejada. Thread.Sleepbloqueará el hilo actual que causa un cambio de contexto. Si está utilizando un grupo de subprocesos, esto también podría hacer que se asigne un nuevo subproceso. Ambas operaciones son bastante pesadas, mientras que la multitarea cooperativa proporcionada por Task.Delayetc. está diseñada para evitar toda esa sobrecarga, maximizar el rendimiento, permitir la cancelación y proporcionar un código más limpio.
Corillian
2
@LucaCremry onesi: I would use Thread.Sleep` para esperar dentro de un método sincrónico. Sin embargo, nunca hago esto en el código de producción; En mi experiencia, todo Thread.Sleeplo que he visto ha sido indicativo de algún tipo de problema de diseño que debe corregirse adecuadamente.
Stephen Cleary
243

La mayor diferencia entre Task.Delayy Thread.Sleepes que Task.Delayestá destinado a ejecutarse de forma asincrónica. No tiene sentido usarlo Task.Delayen código síncrono. Es una idea MUY mala usar Thread.Sleepen código asincrónico.

Normalmente llamarás Task.Delay() con la awaitpalabra clave:

await Task.Delay(5000);

o, si desea ejecutar algún código antes del retraso:

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

¿Adivina qué imprimirá esto? Corriendo por 0.0070048 segundos. Si movemos lo await delayanterior Console.WriteLine, se imprimirá Running durante 5.0020168 segundos.

Veamos la diferencia con 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");
    }
}

Intenta predecir lo que esto imprimirá ...

asíncrono: inicio
asíncrono: ejecución de 0.0070048 segundos
sincronización: inicio
asíncrono: ejecución de 5.0119008 segundos
asíncrono: finalización de
sincronización: ejecución de 5.0020168 segundos
sincronización: finalizado

Además, es interesante notar que Thread.Sleepes mucho más preciso, la precisión de ms no es realmente un problema, mientras que Task.Delaypuede tomar un mínimo de 15-30 ms. La sobrecarga en ambas funciones es mínima en comparación con la precisión de ms que tienen (use StopwatchClass si necesita algo más preciso). Thread.Sleepaún ata tu hilo, Task.Delaysuéltalo para hacer otro trabajo mientras esperas.

Dorus
fuente
15
¿Por qué es "una idea MUY mala usar Thread.Sleep en código asincrónico"?
sunside
69
@sunside Una de las principales ventajas del código asíncrono es permitir que un hilo funcione en múltiples tareas a la vez, evitando el bloqueo de llamadas. Esto evita la necesidad de grandes cantidades de subprocesos individuales y permite que un conjunto de subprocesos atienda muchas solicitudes a la vez. Sin embargo, dado que el código asíncrono generalmente se ejecuta en el conjunto de subprocesos, el bloqueo innecesario de un solo subproceso Thread.Sleep()consume un subproceso completo que de otro modo podría usarse en otro lugar. Si se ejecutan muchas tareas con Thread.Sleep (), existe una alta probabilidad de agotar todos los hilos de threadpool y obstaculizar seriamente el rendimiento.
Ryan
1
Consíguelo. Me faltaba la noción de código asincrónico en el sentido de los asyncmétodos, ya que se recomienda su uso. Básicamente es una mala idea ejecutar Thread.Sleep()un hilo de grupo de subprocesos, no es una mala idea en general. Después de todo, hay TaskCreationOptions.LongRunningcuando ir a la Task.Factory.StartNew()ruta (aunque desanimada) .
sol
66
felicitaciones porawait wait
Eric Wu
2
@Reyhn La documentación sobre esto es que Tasl.Delayutiliza el temporizador del sistema. Dado que "El reloj del sistema" marca "a una velocidad constante", la velocidad de marcación del temporizador del sistema es de aproximadamente 16 ms, cualquier retraso que solicite se redondeará a una serie de tics del reloj del sistema, compensado por el tiempo hasta el primer garrapata. Consulte la documentación de Task.Delay msdn en docs.microsoft.com/en-us/dotnet/api/… y desplácese hacia abajo para ver los comentarios.
Dorus
28

si el subproceso actual se elimina y lo usa Thread.Sleepy se está ejecutando, entonces podría obtener un ThreadAbortException. Con Task.Delayusted siempre puede proporcionar un token de cancelación y matarlo con gracia. Esa es una razón por la que elegiría Task.Delay. ver http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx

También estoy de acuerdo en que la eficiencia no es primordial en este caso.

Balanikas
fuente
2
Supongamos que tenemos la siguiente situación: await Task.Delay(5000). Cuando mato la tarea, obtengo TaskCanceledException(y la suprimo) pero mi hilo sigue vivo. ¡Ordenado! :)
AlexMelw 01 de
24

Quiero agregar algo En realidad, Task.Delayes un mecanismo de espera basado en temporizador. Si observa la fuente , encontrará una referencia a una Timerclase responsable del retraso. Por otro lado, en Thread.Sleeprealidad hace que el hilo actual se suspenda, de esa manera solo está bloqueando y desperdiciando un hilo. En el modelo de programación asíncrona, siempre debe usar Task.Delay()si desea que algo (continuación) suceda después de algún retraso.

encriptado
fuente
'await Task.Delay ()' libera el hilo para hacer otras cosas hasta que expire el temporizador, 100% claro. Pero, ¿qué pasa si no puedo usar 'esperar' ya que el método no tiene el prefijo 'asíncrono'? Entonces solo puedo llamar 'Task.Delay ()'. En ese caso, el hilo todavía está bloqueado, pero tengo la ventaja de cancelar el Retraso () . ¿Es eso correcto?
Erik Stroeken
55
@ErikStroeken Puede pasar tokens de cancelación a subproceso y tarea. Task.Delay (). Wait () se bloqueará, mientras que Task.Delay () solo crea la tarea si se usa sin esperar. Lo que hagas con esa tarea depende de ti, pero el hilo continúa.