Buscar..


Observaciones

Para ejecutar cualquiera de estos ejemplos, llámelos así:

static void Main()
{
    new Program().ProcessDataAsync();
    Console.ReadLine();
}

ASP.NET Configure Await

Cuando ASP.NET maneja una solicitud, se asigna un subproceso desde el grupo de subprocesos y se crea un contexto de solicitud . El contexto de solicitud contiene información sobre la solicitud actual a la que se puede acceder a través de la propiedad estática HttpContext.Current . El contexto de solicitud para la solicitud se asigna al hilo que maneja la solicitud.

Un contexto de solicitud dado solo puede estar activo en un hilo a la vez .

Cuando la ejecución llega a la await , el subproceso que maneja una solicitud se devuelve al grupo de subprocesos mientras se ejecuta el método asíncrono y el contexto de la solicitud es libre para que otro subproceso lo utilice.

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    var products = await dbContext.Products.ToListAsync();

    // Execution resumes on a "random" thread from the pool
    // Execution continues using the original request context.
    return View(products);
}

Cuando la tarea se completa, el grupo de hilos asigna otro hilo para continuar la ejecución de la solicitud. El contexto de solicitud se asigna a este hilo. Este puede o no ser el hilo original.

Bloqueando

Cuando se espera el resultado de una llamada a un método async , pueden surgir puntos muertos sincrónicos . Por ejemplo, el siguiente código resultará en un interbloqueo cuando se IndexSync() :

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    List<Product> products = await dbContext.Products.ToListAsync();

    // Execution resumes on a "random" thread from the pool
    return View(products);
}

public ActionResult IndexSync()
{
    Task<ActionResult> task = Index();

    // Block waiting for the result synchronously
    ActionResult result = Task.Result;

    return result;       
}

Esto se debe a que, de forma predeterminada, la tarea esperada, en este caso db.Products.ToListAsync() capturará el contexto (en el caso de ASP.NET el contexto de la solicitud) e intentará usarlo una vez que se haya completado.

Cuando toda la pila de llamadas es asíncrona, no hay problema porque, una vez await se alcanza la await el hilo original se libera, liberando el contexto de la solicitud.

Cuando Task.Result sincrónica utilizando Task.Result o Task.Wait() (u otros métodos de bloqueo), el subproceso original todavía está activo y conserva el contexto de la solicitud. El método esperado aún funciona de forma asíncrona y una vez que la devolución de llamada intenta ejecutarse, es decir, una vez que la tarea esperada ha regresado, intenta obtener el contexto de la solicitud.

Por lo tanto, el interbloqueo surge porque mientras el subproceso de bloqueo con el contexto de la solicitud está esperando a que se complete la operación asíncrona, la operación asíncrona está tratando de obtener el contexto de la solicitud para poder completarla.

ConfigureAwait

De forma predeterminada, las llamadas a una tarea esperada capturarán el contexto actual e intentarán reanudar la ejecución en el contexto una vez completado.

Al utilizar ConfigureAwait(false) esto se puede evitar y los puntos muertos se pueden evitar.

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    List<Product> products = await dbContext.Products.ToListAsync().ConfigureAwait(false);

    // Execution resumes on a "random" thread from the pool without the original request context
    return View(products);
}

public ActionResult IndexSync()
{
    Task<ActionResult> task = Index();

    // Block waiting for the result synchronously
    ActionResult result = Task.Result;

    return result;       
}

Esto puede evitar puntos muertos cuando es necesario bloquear en código asíncrono, sin embargo, esto conlleva el costo de perder el contexto en la continuación (código después de la llamada a la espera).

En ASP.NET, esto significa que si su código después de una llamada await someTask.ConfigureAwait(false); intenta acceder a la información desde el contexto, por ejemplo, HttpContext.Current.User entonces la información se ha perdido. En este caso, el HttpContext.Current es nulo. Por ejemplo:

public async Task<ActionResult> Index()
{
    // Contains information about the user sending the request
    var user = System.Web.HttpContext.Current.User;

    using (var client = new HttpClient())
    {
        await client.GetAsync("http://google.com").ConfigureAwait(false);
    }

    // Null Reference Exception, Current is null
    var user2 = System.Web.HttpContext.Current.User;

    return View();
}

Si se usa ConfigureAwait(true) (equivalente a no tener ningún ConfigureAwait), entonces el user y el user2 se llenan con los mismos datos.

Por este motivo, a menudo se recomienda usar ConfigureAwait(false) en el código de la biblioteca donde ya no se usa el contexto.

Asíncrono / espera

Vea a continuación un ejemplo sencillo de cómo usar async / await para hacer cosas que requieren mucho tiempo en un proceso en segundo plano, al tiempo que mantiene la opción de hacer otras cosas que no necesitan esperar en las cosas que requieren mucho tiempo para completarse.

Sin embargo, si necesita trabajar con el resultado del método intensivo de tiempo más adelante, puede hacerlo esperando la ejecución.

public async Task ProcessDataAsync()
{
    // Start the time intensive method
    Task<int> task = TimeintensiveMethod(@"PATH_TO_SOME_FILE");

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");

    // Wait for TimeintensiveMethod to complete and get its result
    int x = await task;
    Console.WriteLine("Count: " + x);
}

private async Task<int> TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = await reader.ReadToEndAsync();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

Trabajador de fondo

Vea a continuación un ejemplo simple de cómo usar un objeto BackgroundWorker para realizar operaciones que requieren mucho tiempo en un hilo de fondo.

Necesitas:

  1. Defina un método de trabajo que haga el trabajo intensivo en tiempo y llámelo desde un controlador de eventos para el evento DoWork de un BackgroundWorker .
  2. Inicie la ejecución con RunWorkerAsync . Cualquier argumento requerido por el método de obrero unido a DoWork se puede pasar en a través de la DoWorkEventArgs parámetro para RunWorkerAsync .

Además del evento DoWork , la clase BackgroundWorker también define dos eventos que deben usarse para interactuar con la interfaz de usuario. Estos son opcionales.

  • El evento RunWorkerCompleted se activa cuando los controladores de DoWork han completado.
  • El evento ProgressChanged se activa cuando se llama al método ReportProgress .
public void ProcessDataAsync()
{
    // Start the time intensive method
    BackgroundWorker bw = new BackgroundWorker();
    bw.DoWork += BwDoWork;
    bw.RunWorkerCompleted += BwRunWorkerCompleted;
    bw.RunWorkerAsync(@"PATH_TO_SOME_FILE");

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}

// Method that will be called after BwDoWork exits
private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // we can access possible return values of our Method via the Parameter e
    Console.WriteLine("Count: " + e.Result);
}

// execution of our time intensive Method
private void BwDoWork(object sender, DoWorkEventArgs e)
{
    e.Result = TimeintensiveMethod(e.Argument);
}

private int TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = reader.ReadToEnd();

       for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

Tarea

Vea a continuación un ejemplo simple de cómo usar una Task para hacer cosas que requieren mucho tiempo en un proceso en segundo plano.

Todo lo que necesita hacer es envolver su método intensivo en tiempo en una llamada Task.Run() .

public void ProcessDataAsync()
{
    // Start the time intensive method
    Task<int> t = Task.Run(() => TimeintensiveMethod(@"PATH_TO_SOME_FILE"));

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");

    Console.WriteLine("Count: " + t.Result);
}

private int TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

Hilo

Vea a continuación un ejemplo simple de cómo usar un Thread para hacer cosas que requieren mucho tiempo en un proceso en segundo plano.

public async void ProcessDataAsync()
{
    // Start the time intensive method
    Thread t = new Thread(TimeintensiveMethod);

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}

private void TimeintensiveMethod()
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(@"PATH_TO_SOME_FILE"))
    {
        string v = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            v.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");
}

Como puede ver, no podemos devolver un valor de nuestro TimeIntensiveMethod porque Thread espera un método vacío como parámetro.

Para obtener un valor de retorno de un Thread use un evento o lo siguiente:

int ret;
Thread t= new Thread(() => 
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file))
    {
        string s = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something to demonstrate the coolness of await-async
    ret = new Random().Next(100);
});

t.Start();
t.Join(1000);
Console.Writeline("Count: " + ret);

Tarea "Ejecutar y olvidar" extensión

En ciertos casos (por ejemplo, el registro) puede ser útil ejecutar una tarea y no esperar el resultado. La siguiente extensión permite ejecutar la tarea y continuar la ejecución del código de descanso:

public static class TaskExtensions
{
    public static async void RunAndForget(
        this Task task, Action<Exception> onException = null)
    {
        try
        {
            await task;
        }
        catch (Exception ex)
        {
            onException?.Invoke(ex);
        }
    }
}

El resultado se espera solo dentro del método de extensión. Como se utiliza async / await , es posible detectar una excepción y llamar a un método opcional para manejarlo.

Un ejemplo de cómo usar la extensión:

var task = Task.FromResult(0); // Or any other task from e.g. external lib.
task.RunAndForget(
    e =>
    {
        // Something went wrong, handle it.
    });


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow