C# Language ASP.NET Configure Await


Esempio

Quando ASP.NET gestisce una richiesta, viene assegnato un thread dal pool di thread e viene creato un contesto di richiesta . Il contesto della richiesta contiene informazioni sulla richiesta corrente a cui è possibile accedere tramite la proprietà statica HttpContext.Current . Il contesto della richiesta per la richiesta viene quindi assegnato al thread che gestisce la richiesta.

Un determinato contesto di richiesta può essere attivo solo su un thread alla volta .

Quando l'esecuzione giunge in await , il thread che gestisce una richiesta viene restituito al pool di thread mentre viene eseguito il metodo asincrono e il contesto della richiesta è gratuito per un altro thread da utilizzare.

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

Al termine dell'attività, il pool di thread assegna un altro thread per continuare l'esecuzione della richiesta. Il contesto della richiesta viene quindi assegnato a questo thread. Questo potrebbe essere o non essere il thread originale.

Blocco

Quando si attende il risultato di una chiamata al metodo async , possono verificarsi deadlock sincroni . Ad esempio, il seguente codice genererà un deadlock quando viene chiamato 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;       
}

Questo perché, per impostazione predefinita, l'attività attesa, in questo caso db.Products.ToListAsync() acquisisce il contesto (nel caso di ASP.NET il contesto della richiesta) e tenta di utilizzarlo una volta completato.

Quando l'intero stack di chiamate è asincrono, non ci sono problemi perché, una volta in await viene raggiunto, il thread originale viene rilasciato, liberando il contesto della richiesta.

Quando si blocca in modo sincrono utilizzando Task.Result o Task.Wait() (o altri metodi di blocco) il thread originale è ancora attivo e conserva il contesto della richiesta. Il metodo atteso funziona ancora in modo asincrono e una volta che il callback tenta di essere eseguito, ovvero quando viene restituita l'attività attesa, tenta di ottenere il contesto della richiesta.

Pertanto il deadlock si verifica perché mentre il thread di blocco con il contesto della richiesta è in attesa del completamento dell'operazione asincrona, l'operazione asincrona sta tentando di ottenere il contesto della richiesta per il completamento.

ConfigureAwait

Per impostazione predefinita, le chiamate a un'attività attesa acquisiscono il contesto corrente e tentano di riprendere l'esecuzione nel contesto una volta completato.

Usando ConfigureAwait(false) questo può essere prevenuto e le deadlock possono essere evitate.

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

Questo può evitare deadlock quando è necessario bloccare il codice asincrono, tuttavia ciò comporta il costo di perdere il contesto nella continuazione (codice dopo la chiamata in attesa).

In ASP.NET questo significa che se il tuo codice segue una chiamata per await someTask.ConfigureAwait(false); tenta di accedere alle informazioni dal contesto, ad esempio HttpContext.Current.User quindi le informazioni sono state perse. In questo caso, HttpContext.Current è null. Per esempio:

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

Se viene utilizzato ConfigureAwait(true) (equivalente a non avere affatto ConfigureAwait), sia l' user che l' user2 vengono popolati con gli stessi dati.

Per questo motivo, si consiglia spesso di utilizzare ConfigureAwait(false) nel codice della libreria in cui il contesto non è più utilizzato.