Eseguire operazioni fire and forget in ASP.NET

di Cristian Civera, in ASP.NET 4.5, ASP.NET MVC 4,

In ASP.NET 4.5 possiamo eseguire operazioni asincrone sfruttando le keyword async e await di C# 5. Per esempio, in un controller ASP.NET MVC è sufficiente il codice in basso per poter eseguire la chiamata a SomeMethodAsync su un altro thread, liberando il thread di IIS:

public async Task<ActionResult> SomeAction() 
{
  await SomeMethodAsync();

  return this.View();
}

private async Task SomeMethodAsync()
{
  await FirstOperationAsync();
  await SecondOperationAsync();
}

Nell'esempio in alto, è il framework stesso a predisporre un SynchronizationContext che si occupa di effettuare il marshalling del codice nel contesto di richiesta originale, una volta che l'operazione asincrona è terminata. Questa funzionalità è sicuramente molto comoda, perchè ci permette di trascurare i dettagli su cosa sta accadendo dietro le quinte, rendendo il codice molto simile a quello che scriveremmo nel caso fosse sincrono.

Non bisogna mai dimenticare, però, che in realtà ciò che effettivamente accade è più complesso e può avere delle implicazioni non immediatamente prevedibili. Un esempio è il caso di un'operazione di tipo fire and forget, di cui cioé non attendiamo il risultato prima del completamento, come nell'esempio in basso:

public ActionResult SomeAction()
{
  // non usiamo await, avviamo l'operazione senza attendere il completamento
  SomeMethodAsync();

  // ritorniamo il risultato prima che l'invocazione
  // a SomeMethodAsync sia terminata
  return this.View();
}

private async Task SomeMethodAsync()
{
  // queste operazioni potrebbero generare un deadlock
  await FirstOperationAsync();
  await SecondOperationAsync();
}

Quando l'esecuzione di FirstOperationAsync termina, il contesto di richiesta originale potrebbe non essere più disponibile, perché la view è stata già ritornata al browser e la richiesta si è effettivamente chiusa; ciò impedisce al SynchronizationContext di effettuare il marshalling, di fatto bloccando l'esecuzione di SomeMethodAsync.

Il modo corretto per ovviare a questo problema è quello di utilizzare l'opzione ConfigureAwait(false):

private async Task SomeMethodAsync()
{
  await FirstOperationAsync().ConfigureAwait(false);
  await SecondOperationAsync().ConfigureAwait(false);
}

In questo modo, il framework non tenterà di ripristinare il contesto di richiesta originale, consentendo quindi al task asincrono di terminare correttamente anche se questo non è più disponibile.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi