He leído todo lo que puedo encontrar sobre programación asíncrona en asp.net (c #). He comprendido la mayor parte de cómo se supone que funciona y cuándo debe usarse. Sin embargo, encuentro ejemplos básicos que no funcionan como esperaba. Sin Task.Run, no parece que se ejecute de forma asincrónica.

¿Alguien puede decirme qué me estoy perdiendo en este ejemplo?

Di que el código es así

public async Task SubTask2()
{
    LongRunningOperation2();
    Response.Write("<br>------------------------ Finished -------------------------<br>");
}

private async Task<Boolean> LongRunningOperation1()
{
    int counter;

    for (counter = 0; counter < 50000; counter++)
    {
        Response.Write(counter + "<br>");
    }
    return await Task.FromResult<bool>(true);
}

private async Task<Boolean> LongRunningOperation2()
{
    await LongRunningOperation1();
    Response.Write("<br>------------------------ Long Task -------------------------<br>");
    return true;
}

¿No debería LongRunningOperation2() volver a SubTask2() e imprimir "terminado" antes o mientras escribe los números? En cambio, se imprime terminado al final. El uso de Task.Run funciona como se esperaba, pero no veo el sentido de no usar Task.Run

1
Krieger 14 nov. 2017 a las 15:24

2 respuestas

La mejor respuesta

Una consideración de diseño del patrón async / await es que a veces el código que tiene una firma async puede regresar sincrónicamente (inmediatamente), quizás debido al almacenamiento en caché o al almacenamiento en búfer de datos locales (lectura de datos de un socket, tal vez, y tener datos de repuesto para consumir del búfer), o tal vez debido a que IoC, etc., proporciona una implementación sincrónica de una firma asincrónica. En ese escenario, todo el motor está diseñado para optimizar no haciendo nada como devoluciones de llamada, sino que continúa ejecutándose sincrónicamente. Este no es un caso límite: las actualizaciones recientes de C # han extendido esto al agregar soporte para esperables personalizados (en particular: ValueTask<T>) para hacer esto aún más eficiente en el caso de que algo se complete con un resultado sincrónico pero no trivial.

El propósito de async es facilitar escenarios que tienen componentes genuinamente async, liberando el hilo para hacer cosas más útiles que esperar a que se complete una operación async. No se trata de paralelización.

En su caso, todo su código es en realidad sincrónico, por lo que continúa ejecutándose sincrónicamente hasta el final.

1
Marc Gravell 14 nov. 2017 a las 12:48

Primero, falta await en SubTask2(), por lo que en realidad se ejecuta sincrónicamente (espera a que LongRunningOperation2() termine). En segundo lugar, incluso con la ejecución asincrónica, "terminado" no se imprimirá antes de que LongRunningOperation2() termine su trabajo. El objetivo de usarlo es liberar el hilo, para que pueda hacer algún otro trabajo mientras tanto (por ejemplo, manejar otra solicitud).

0
borkovski 14 nov. 2017 a las 12:40