Pregunta

todavía estoy aprendiendo el async/await, así que discúlpeme si pregunto algo obvio.Considere el siguiente ejemplo:

class Program  {

    static void Main(string[] args) {
        var result = FooAsync().Result;
        Console.WriteLine(result);
    }

    static async Task<int> FooAsync() {

        var t1 = Method1Async();
        var t2 = Method2Async();

        var result1 = await t1;
        var result2 = await t2;

        return result1 + result2;

    }

    static Task<int> Method1Async() {
        return Task.Run(
            () => {
                Thread.Sleep(1000);
                return 11;
            }
        );
    }

    static Task<int> Method2Async() {
        return Task.Run(
            () => {
                Thread.Sleep(1000);
                return 22;
            }
        );
    }

}

Esto se comporta como se esperaba e imprime "33" en la consola.

Si reemplazo el segundo await con una espera explícita...

static async Task<int> FooAsync() {

    var t1 = Method1Async();
    var t2 = Method2Async();

    var result1 = await t1;
    var result2 = t2.Result;

    return result1 + result2;

}

... Parece que tengo el mismo comportamiento.

¿Son estos dos ejemplos completamente equivalentes?

Y si son equivalentes en este caso, ¿existen otros casos en los que reemplazar el último await ¿Por una espera explícita haría la diferencia?

¿Fue útil?

Solución 3

Bien, creo que ya me di cuenta de esto, así que permítanme resumirlo, en lo que espero sea una explicación más completa que las respuestas proporcionadas hasta ahora...

Respuesta corta

Reemplazo del segundo await con una espera explícita no tendrá ningún efecto apreciable en una aplicación de consola, pero bloqueará el hilo de la interfaz de usuario de una aplicación WPF o WinForms durante la espera.

Además, el manejo de excepciones es ligeramente diferente (como señaló Stephen Cleary).

Respuesta larga

En pocas palabras, el await Haz esto:

  1. Si la tarea esperada ya finalizó, simplemente recupera su resultado y continúa.
  2. Si no es así, publicaciones la continuación (el resto del método después del await) hacia actual contexto de sincronización, si lo hay.Esencialmente, await está intentando devolvernos al punto de partida.
    • Si no hay un contexto actual, simplemente usa el original. Programador de tareas, que suele ser un grupo de subprocesos.

El segundo (y el tercero y así sucesivamente...) await hace lo mismo.

Dado que las aplicaciones de consola normalmente no tienen contexto de sincronización, las continuaciones normalmente serán manejadas por el grupo de subprocesos, por lo que no hay problema si bloqueamos dentro de la continuación.

WinForms o WPF, por otro lado, tienen implementado el contexto de sincronización además de su bucle de mensajes.Por lo tanto, await ejecutado en un subproceso de la interfaz de usuario (eventualmente) también ejecutará su continuación en el subproceso de la interfaz de usuario.Si bloqueamos la continuación, bloqueará el bucle de mensajes y hará que la interfaz de usuario no responda hasta que la desbloqueemos.OTOH, si tan solo await, publicará claramente las continuaciones que eventualmente se ejecutarán en el hilo de la interfaz de usuario, sin bloquear nunca el hilo de la interfaz de usuario.

En el siguiente formulario de WinForms, que contiene un botón y una etiqueta, usando await mantiene la interfaz de usuario receptiva en todo momento (tenga en cuenta la async delante del controlador de clic):

public partial class Form1 : Form {

    public Form1() {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e) {
        var result = await FooAsync();
        label1.Text = result.ToString();
    }

    static async Task<int> FooAsync() {

        var t1 = Method1Async();
        var t2 = Method2Async();

        var result1 = await t1;
        var result2 = await t2;

        return result1 + result2;

    }

    static Task<int> Method1Async() {
        return Task.Run(
            () => {
                Thread.Sleep(3000);
                return 11;
            }
        );
    }

    static Task<int> Method2Async() {
        return Task.Run(
            () => {
                Thread.Sleep(5000);
                return 22;
            }
        );
    }

}

Si reemplazamos el segundo await en FooAsync con t2.Result, continuaría respondiendo durante aproximadamente 3 segundos después de hacer clic en el botón y luego se congelaría durante aproximadamente 2 segundos:

  • La continuación después de la primera. await esperará cortésmente su turno para ser programado en el hilo de la interfaz de usuario, lo que sucedería después Method1Async() la tarea finaliza, es decirdespués de unos 3 segundos,
  • en cuyo punto el t2.Result bloqueará bruscamente el hilo de la interfaz de usuario hasta que Method2Async() La tarea finaliza, aproximadamente 2 segundos después.

Si eliminamos el async en frente de button1_Click y reemplazó su await con FooAsync().Result se estancaría:

  • El hilo de la interfaz de usuario esperaría FooAsync() tarea por terminar,
  • que esperaría a que terminara su continuación,
  • que esperaría a que el hilo de la interfaz de usuario esté disponible,
  • lo cual no lo es, ya que está bloqueado por el FooAsync().Result.

El artículo "Await, SynchronizationContext y aplicaciones de consola" de Stephen Toub fue invaluable para mí a la hora de comprender este tema.

Otros consejos

Su versión de reemplazo bloquea el hilo de llamada esperando a que finalice la tarea.Es difícil ver una diferencia visible en una aplicación de consola como esa, ya que estás bloqueando intencionalmente en Principal, pero definitivamente no son equivalentes.

No son equivalentes.

Task.Result bloques hasta que el resultado esté disponible.Como explico en mi blog, esto puede causar puntos muertos si tienes un async contexto que requiere acceso exclusivo (por ejemplo, una interfaz de usuario o una aplicación ASP.NET).

También, Task.Result envolverá cualquier excepción en AggregateException, por lo que el manejo de errores es más difícil si bloquea sincrónicamente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top