C# Language ASP.NET Configure Await


Exemple

Lorsque ASP.NET gère une demande, un thread est attribué à partir du pool de threads et un contexte de demande est créé. Le contexte de la demande contient des informations sur la requête en cours, accessibles via la propriété statique HttpContext.Current . Le contexte de demande pour la demande est ensuite affecté au thread qui gère la demande.

Un contexte de requête donné ne peut être actif que sur un thread à la fois .

Lorsque l'exécution est en await , le thread qui gère une demande est renvoyé au pool de threads pendant que la méthode asynchrone s'exécute et que le contexte de la demande est libre pour qu'un autre thread puisse l'utiliser.

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);
}

Lorsque la tâche se termine, le pool de threads assigne un autre thread pour continuer l'exécution de la demande. Le contexte de la demande est ensuite affecté à ce thread. Cela peut être ou ne pas être le fil d'origine.

Blocage

Lorsque le résultat d'un appel de méthode async est attendu, des blocages synchrones peuvent survenir. Par exemple, le code suivant provoquera un blocage lorsque IndexSync() est appelé:

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;       
}

En effet, par défaut, la tâche attendue, dans ce cas, db.Products.ToListAsync() capturera le contexte (dans le cas d'ASP.NET, le contexte de la demande) et tentera de l'utiliser une fois terminé.

Lorsque toute la pile d'appels est asynchrone, il n'y a pas de problème car, une fois que l' await est atteinte, le thread d'origine est libéré, libérant le contexte de la requête.

Lorsque nous bloquons de manière synchrone à l'aide de Task.Result ou Task.Wait() (ou d'autres méthodes de blocage), le thread d'origine est toujours actif et conserve le contexte de la requête. La méthode attendue fonctionne toujours de manière asynchrone et une fois que le rappel tente de s'exécuter, c'est-à-dire une fois la tâche attendue renvoyée, elle tente d'obtenir le contexte de la requête.

Par conséquent, le blocage se produit car, pendant que le thread de blocage avec le contexte de requête attend la fin de l'opération asynchrone, l'opération asynchrone tente d'obtenir le contexte de la requête pour terminer.

ConfigureAwait

Par défaut, les appels à une tâche attendue captureront le contexte actuel et tenteront de reprendre l'exécution sur le contexte une fois terminé.

En utilisant ConfigureAwait(false) cela peut être évité et les blocages peuvent être évités.

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;       
}

Cela peut éviter les blocages quand il est nécessaire de bloquer le code asynchrone, mais cela entraîne la perte du contexte dans la suite (code après l'appel à attendre).

Dans ASP.NET, cela signifie que si votre code suit un appel pour await someTask.ConfigureAwait(false); tente d'accéder aux informations du contexte, par exemple HttpContext.Current.User alors les informations ont été perdues. Dans ce cas, le HttpContext.Current est nul. Par exemple:

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 ConfigureAwait(true) est utilisé (équivalent à ne pas avoir ConfigureAwait du tout) alors à la fois l' user et user2 sont peuplées avec les mêmes données.

Pour cette raison, il est souvent recommandé d'utiliser ConfigureAwait(false) dans le code de la bibliothèque où le contexte n'est plus utilisé.