¿Cuál es la diferencia entre Task.Start / Wait y Async / Await?

207

Puede que me falte algo, pero ¿cuál es la diferencia entre hacer:

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
fuente

Respuestas:

395

Puedo estar olvidando algo

Usted está.

¿Cuál es la diferencia entre hacer Task.Waity await task?

Pides tu almuerzo del camarero en el restaurante. Un momento después de dar su orden, un amigo entra y se sienta a su lado y comienza una conversación. Ahora tienes dos opciones. Puede ignorar a su amigo hasta que se complete la tarea; puede esperar hasta que llegue su sopa y no hacer nada más mientras espera. O puede responder a su amigo, y cuando su amigo deja de hablar, el camarero le traerá su sopa.

Task.Waitbloquea hasta que se complete la tarea: ignora a su amigo hasta que se complete la tarea. awaitsigue procesando mensajes en la cola de mensajes, y cuando se completa la tarea, pone en cola un mensaje que dice "retomar donde lo dejó después de esperar". Hablas con tu amigo, y cuando hay un descanso en la conversación, llega la sopa.

Eric Lippert
fuente
55
@ronag No, no lo es. ¿Cómo le gustaría si esperar un mensaje Taskque demore 10 ms realmente ejecute una duración de 10 horas Tasken su hilo, bloqueándolo durante las 10 horas completas?
svick
62
@StrugglingCoder: el operador de espera no hace nada más que evaluar su operando y luego devolver inmediatamente una tarea al llamante actual . A la gente se le ocurre la idea de que la asincronía solo se puede lograr descargando el trabajo en hilos, pero eso es falso. Puede cocinar el desayuno y leer el periódico mientras la tostada está en la tostadora sin contratar a un cocinero para que la cuide. La gente dice que bien, debe haber un hilo, un trabajador, escondido dentro de la tostadora, pero te aseguro que si miras en la tostadora, no hay ningún pequeño hombre allí mirando la tostada.
Eric Lippert
11
@StrugglingCoder: Entonces, ¿quién está haciendo el trabajo que pides? Tal vez otro hilo está haciendo el trabajo, y ese hilo ha sido asignado a una CPU, por lo que el trabajo se está haciendo. Tal vez el trabajo lo está haciendo el hardware y no hay ningún hilo en absoluto. Pero seguramente, usted dice, debe haber algún hilo en el hardware . No. El hardware existe por debajo del nivel de hilos. ¡No necesita haber hilo! Podría beneficiarse al leer el artículo de Stephen Cleary No hay hilo.
Eric Lippert
66
@StrugglingCoder: Ahora, pregunta, supongamos que se está haciendo un trabajo asincrónico y no hay hardware y no hay otro hilo. ¿Cómo es esto posible? Bueno, supongamos que lo que esperabas genera una serie de mensajes de Windows , cada uno de los cuales funciona un poco. Ahora que pasa? Devuelve el control al bucle de mensajes, comienza a extraer mensajes de la cola, realiza un poco de trabajo cada vez, y el último trabajo que se realiza es "ejecutar la continuación de la tarea". ¡Sin hilo extra!
Eric Lippert
8
@StrugglingCoder: Ahora, piensa en lo que acabo de decir. Ya sabes que así es como funciona Windows . Ejecuta una serie de movimientos del mouse y clics de botones y otras cosas. Los mensajes se ponen en cola, se procesan a su vez, cada mensaje hace que se realice una pequeña cantidad de trabajo, y cuando todo está hecho, el sistema continúa. Async en un hilo no es más que lo que ya estás acostumbrado: dividir las tareas grandes en pequeños trozos, ponerlas en cola y ejecutar todos los pequeños trozos en algún orden. Algunas de esas ejecuciones hacen que otros trabajos se pongan en cola y la vida continúa. Un hilo!
Eric Lippert
121

Para demostrar la respuesta de Eric aquí hay un código:

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
fuente
27
+1 para el código (es mejor ejecutar una vez que leer cien veces). Pero la frase " //If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!" es engañosa. Al presionar el botón con el t.Wait();controlador de eventos de clic de botón ButtonClick()no es posible presionar nada y luego ver algo en la consola y actualizar la etiqueta "hasta que se complete esta tarea" ya que la GUI está congelada y no responde, es decir, cualquier clic o interacción con la GUI están siendo PERDIDOS hasta la finalización de la tarea de espera
Gennady Vanin Геннадий Ванин
2
Supongo que Eric supone que tienes una comprensión básica de la API de la tarea. Miro ese código y me digo "sí, t.Waitva a bloquear en el hilo principal hasta que se complete la tarea".
The Muffin Man el
50

Este ejemplo demuestra la diferencia muy claramente. Con async / wait, el hilo de llamada no se bloqueará y continuará ejecutándose.

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

Salida de DoAsTask:

[1] Inicio del programa
[1] 1 - Inicio
[1] 2 - Tarea iniciada
[3] A - Comenzó algo
[3] B - Completado algo
[1] 3 - Tarea completada con el resultado: 123
[1] Fin del programa

Salida DoAsAsync:

[1] Inicio del programa
[1] 1 - Inicio
[1] 2 - Tarea iniciada
[3] A - Comenzó algo
[1] Fin del programa
[3] B - Completado algo
[3] 3 - Tarea completada con el resultado: 123

Actualización: ejemplo mejorado al mostrar la ID del hilo en la salida.

Mas
fuente
44
Pero si lo hago: nueva tarea (DoAsTask) .Start (); en lugar de DoAsAsync (); obtengo la misma funcionalidad, entonces, ¿dónde está el beneficio de esperar?
omriman12
1
Con su propuesta, el resultado de la tarea debe evaluarse en otro lugar, tal vez otro método o una lambda. Async-await hace que el código asincrónico sea más fácil de seguir. Es solo un potenciador de sintaxis.
Mas
@Mas No entiendo por qué el Fin del programa es después de A - Comenzó algo. Según tengo entendido cuando se trata de esperar, el proceso de palabras clave debe ir inmediatamente al contexto principal y luego regresar.
@JimmyJimm Desde mi entendimiento, Task.Factory.StartNew hará girar un nuevo hilo para ejecutar DoSomethingThatTakesTime. Como tal, no hay garantía de si el Fin del programa o A - Comenzado algo se ejecutará primero.
RiaanDP
@JimmyJimm: He actualizado la muestra para mostrar las ID de hilo. Como puede ver, "Fin de programa" y "A - Comenzó algo" se ejecutan en diferentes subprocesos. Entonces, en realidad, el orden no es determinista.
Mas
10

Wait (), hará que se ejecute el código potencialmente asíncrono de manera sincronizada. esperar no lo hará.

Por ejemplo, tiene una aplicación web asp.net. UsuarioA llama / getUser / 1 punto final. El grupo de aplicaciones asp.net elegirá un hilo del grupo de hilos (Thread1) y este hilo hará una llamada http. Si espera Wait (), este hilo se bloqueará hasta que se resuelva la llamada http. Mientras está esperando, si UserB llama / getUser / 2, entonces, el grupo de aplicaciones necesitará servir otro hilo (Thread2) para volver a hacer una llamada http. Acaba de crear (Bueno, en realidad se obtuvo del grupo de aplicaciones) otro subproceso sin razón, porque no puede usar Thread1 que fue bloqueado por Wait ().

Si usa await en Thread1, SyncContext gestionará la sincronización entre Thread1 y la llamada http. Simplemente, notificará una vez que se realice la llamada http. Mientras tanto, si UserB llama / getUser / 2, entonces usará Thread1 nuevamente para hacer una llamada http, porque fue liberada una vez que estaba esperando que la golpearan. Luego, otra solicitud puede usarlo, aún más. Una vez que se realiza la llamada http (usuario1 o usuario2), Thread1 puede obtener el resultado y regresar a la persona que llama (cliente). Thread1 se usó para múltiples tareas.

Teoman shipahi
fuente
9

En este ejemplo, no mucho, prácticamente. Si está esperando una Tarea que regrese en un subproceso diferente (como una llamada WCF) o ceda el control al sistema operativo (como Archivo IO), esperar usará menos recursos del sistema al no bloquear un subproceso.

foson
fuente
3

En el ejemplo anterior, puede usar "TaskCreationOptions.HideScheduler" y modificar en gran medida el método "DoAsTask". El método en sí no es asíncrono, como sucede con "DoAsAsync" porque devuelve un valor de "Tarea" y está marcado como "asíncrono", haciendo varias combinaciones, así es como me da exactamente lo mismo que usar "asíncrono / esperar" :

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
usuario8545699
fuente