Primeros pasos con async-await

vacío asíncrono

Puede usar void (en lugar de Task) como tipo de retorno de un método asíncrono. Esto resultará en una acción de “disparar y olvidar”:

public void DoStuff()
{
    FireAndForgetAsync();
}
    
private async void FireAndForgetAsync()
{
    await Task.Delay(1000);
    throw new Exception(); //will be swallowed
}

Como está devolviendo void, no puede await FireAndForgetAsync. No podrá saber cuándo finaliza el método, y cualquier excepción generada dentro del método async void será procesada.

uso sencillo

Se necesitan tres cosas para usar async-await:

  • El objeto Tarea: Este objeto es devuelto por un método que se ejecuta de forma asíncrona. Le permite controlar la ejecución del método.
  • La palabra clave await: “Espera” una Tarea. Coloque esta palabra clave antes de la Tarea para esperar asíncronamente a que termine
  • La palabra clave async: todos los métodos que usan la palabra clave await deben marcarse como async

Un pequeño ejemplo que demuestra el uso de estas palabras clave

public async Task DoStuffAsync()
{
    var result = await DownloadFromWebpageAsync(); //calls method and waits till execution finished
    var task = WriteTextAsync(@"temp.txt", result); //starts saving the string to a file, continues execution right await
    Debug.Write("this is executed parallel with WriteTextAsync!"); //executed parallel with WriteTextAsync!
    await task; //wait for WriteTextAsync to finish execution
}

private async Task<string> DownloadFromWebpageAsync()
{
    using (var client = new WebClient())
    {
        return await client.DownloadStringTaskAsync(new Uri("http://stackoverflow.com"));
    }
}

private async Task WriteTextAsync(string filePath, string text)
{
    byte[] encodedText = Encoding.Unicode.GetBytes(text);

    using (FileStream sourceStream = new FileStream(filePath, FileMode.Append))
    {
        await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
    }
} 

Algunas cosas a tener en cuenta:

  • Puede especificar un valor de retorno de una operación asíncrona con Task<string> o similar. La palabra clave await espera hasta que finaliza la ejecución del método y devuelve la cadena.
  • el objeto Tarea simplemente contiene el estado de ejecución del método, se puede utilizar como cualquier otra variable.
  • si se lanza una excepción (por ejemplo, por WebClient) aparece la primera vez que se usa la palabra clave await (en este ejemplo, en la línea var result (...))
  • Se recomienda nombrar los métodos que devuelven el objeto Task como MethodNameAsync

ejecutar código síncrono asíncrono

Si desea ejecutar código síncrono de forma asíncrona (por ejemplo, cálculos extensos de CPU), puede usar Task.Run(() => {}).

public async Task DoStuffAsync()
{
    await DoCpuBoundWorkAsync();
}


private async Task DoCpuBoundWorkAsync()
{
    await Task.Run(() =>
    {
        for (long i = 0; i < Int32.MaxValue; i++)
        {
            i = i ^ 2;
        }
    });
}

el objeto Tarea

El objeto Tarea es un objeto como cualquier otro si quita las palabras clave async-await.

Considere este ejemplo:

public async Task DoStuffAsync()
{
    await WaitAsync();
    await WaitDirectlyAsync();
}

private async Task WaitAsync()
{
    await Task.Delay(1000);
}

private Task WaitDirectlyAsync()
{
    return Task.Delay(1000);
}

La diferencia entre estos dos métodos es simple:

  • WaitAsync espera a que finalice Task.Delay y luego regresa.
  • WaitDirectlyAsync no espera y simplemente devuelve el objeto Task al instante.

Cada vez que usa la palabra clave await, el compilador genera código para tratar con ella (y el objeto Task que espera).

  • Al llamar a await WaitAsync(), esto sucede dos veces: una vez en el método de llamada y otra vez en el método mismo.
  • Al llamar a await WaitDirectlyAsync esto sucede solo una vez (en el método de llamada). Por lo tanto, archivaría un poco de aceleración en comparación con await WaitAsync().

Cuidado con las excepciones: Las excepciones aparecerán la primera vez que se en espera de una Tarea. Ejemplo:

private async Task WaitAsync()
{
    try
    {
        await Task.Delay(1000);
    }
    catch (Exception ex)
    {
        //this might execute
        throw;
    }
}

private Task WaitDirectlyAsync()
{
    try
    {
        return Task.Delay(1000);
    }
    catch (Exception ex)
    {
        //this code will never execute!
        throw;
    }
}