Prática recomendada para chamar ConfigureAwait para todo o código do lado do servidor

Quando você tem código do lado do servidor (isto é, algum ApiController ) e suas funções são assíncronas – então elas retornam Task – é considerada uma boa prática que, a qualquer momento, você aguarde as funções que chamar de ConfigureAwait(false) ?

Eu tinha lido que é mais performático, uma vez que não tem que mudar os contextos de thread de volta ao contexto do thread original. No entanto, com o ASP.NET Web Api, se sua solicitação está chegando em um thread, e você aguarda alguma function e chamar ConfigureAwait(false) que potencialmente poderia colocá-lo em um thread diferente quando você está retornando o resultado final da sua function ApiController .

Eu digitei um exemplo do que estou falando abaixo:

 public class CustomerController : ApiController { public async Task Get(int id) { // you are on a particular thread here var customer = await SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false); // now you are on a different thread! will that cause problems? return customer; } } 

Atualização: O ASP.NET Core não possui um SynchronizationContext . Se você estiver no ASP.NET Core, não importa se você usa ConfigureAwait(false) ou não.

Para ASP.NET “Full” ou “Classic” ou qualquer outra coisa, o restante desta resposta ainda se aplica.

Post original (para ASP.NET não núcleo):

Este vídeo da equipe do ASP.NET tem as melhores informações sobre como usar o async no ASP.NET.

Eu tinha lido que é mais performático, uma vez que não tem que mudar os contextos de thread de volta ao contexto do thread original.

Isso é verdade com os aplicativos de interface do usuário, onde há apenas um thread de interface do usuário para o qual você precisa “sincronizar”.

No ASP.NET, a situação é um pouco mais complexa. Quando um método async retoma a execução, ele captura um thread do pool de threads do ASP.NET. Se você desativar a captura de contexto usando ConfigureAwait(false) , o encadeamento continuará executando o método diretamente. Se você não desativar a captura de contexto, o encadeamento entrará novamente no contexto da solicitação e continuará executando o método.

Portanto, ConfigureAwait(false) não salva um salto de thread no ASP.NET; Ele economiza a reinput do contexto da solicitação, mas isso é normalmente muito rápido. ConfigureAwait(false) pode ser útil se você estiver tentando fazer uma pequena quantidade de parallel processing de uma solicitação, mas realmente a TPL é mais adequada para a maioria desses cenários.

No entanto, com o ASP.NET Web Api, se sua solicitação está chegando em um thread, e você aguarda alguma function e chamar ConfigureAwait (false) que potencialmente poderia colocá-lo em um thread diferente quando você está retornando o resultado final da sua function ApiController .

Na verdade, apenas fazer uma await pode fazer isso. Depois que o método async await , o método será bloqueado, mas o thread retornará ao pool de threads. Quando o método está pronto para continuar, qualquer encadeamento é retirado do conjunto de encadeamentos e usado para continuar o método.

A única diferença que o ConfigureAwait faz no ASP.NET é se esse segmento entra no contexto de solicitação ao retomar o método.

Eu tenho mais informações básicas no meu artigo do MSDN sobre SynchronizationContext e minha postagem no blog do async .

Breve resposta à sua pergunta: Não. Você não deve chamar ConfigureAwait(false) no nível do aplicativo assim.

TL; DR versão da resposta longa: Se você está escrevendo uma biblioteca onde você não conhece o seu consumidor e não precisa de um contexto de synchronization (que você não deve em uma biblioteca eu acredito), você deve sempre usar ConfigureAwait(false) . Caso contrário, os consumidores de sua biblioteca podem enfrentar deadlocks consumindo seus methods asynchronouss de maneira bloqueada. Isso depende da situação.

Aqui está uma explicação mais detalhada sobre a importância do método ConfigureAwait (uma citação do meu post no blog):

Quando você está aguardando um método com await keyword, o compilador gera um monte de código em nome de você. Um dos propósitos desta ação é manipular a synchronization com o thread da interface do usuário (ou principal). O componente chave desse recurso é o SynchronizationContext.Current que obtém o contexto de synchronization para o segmento atual. SynchronizationContext.Current é preenchido dependendo do ambiente em que você está. O método GetAwaiter da tarefa procura por SynchronizationContext.Current . Se o contexto de synchronization atual não for nulo, a continuação que é passada para esse awaiter será enviada de volta para esse contexto de synchronization.

Ao consumir um método, que usa os novos resources de linguagem assíncrona, de forma bloqueada, você terá um deadlock se tiver um SynchronizationContext disponível. Quando você estiver consumindo esses methods de maneira bloqueada (aguardando o método Task with Wait ou obtendo o resultado diretamente da propriedade Result da tarefa), bloqueará o thread principal ao mesmo tempo. Quando eventualmente a Tarefa concluir dentro desse método no conjunto de encadeamentos, ela chamará a continuação para postar de volta no encadeamento principal porque SynchronizationContext.Current está disponível e capturado. Mas há um problema aqui: o thread da interface do usuário está bloqueado e você tem um deadlock!

Além disso, aqui estão dois ótimos artigos para você que são exatamente para sua pergunta:

  • A Receita Perfeita para se Fotografar no Pé – Terminando com um Impasse Usando os Recursos de Linguagem Assíncrona do C # 5.0
  • Bibliotecas de cliente .NET assíncronas para sua API HTTP e reconhecimento dos efeitos ruins de asynchronous / aguardem

Finalmente, há um excelente vídeo de Lucian Wischik exatamente sobre este tópico: Os methods de biblioteca assíncrona devem considerar o uso de Task.ConfigureAwait (false) .

Espero que isto ajude.

Eu tenho algumas reflexões gerais sobre a implementação da Task :

  1. A tarefa é descartável, mas não devemos usá-la.
  2. ConfigureAwait foi introduzido no 4.5. Task foi introduzida no 4.0.
  3. Threads .NET sempre usados ​​para fluir o contexto (consulte C # via livro CLR), mas na implementação padrão de Task.ContinueWith eles não b / c foi percebido alternância de contexto é caro e está desativado por padrão.
  4. O problema é um desenvolvedor de biblioteca não deve se importar se seus clientes precisam de stream de contexto ou não, portanto, não deve decidir se o stream do contexto ou não.
  5. [Adicionado depois] O fato de que não há resposta autoritativa e referência adequada e continuamos a lutar contra isso significa que alguém não fez o seu trabalho direito.

Eu tenho algumas postagens sobre o assunto, mas minha sugestão, além da resposta bacana de Tugberk, é que você deve tornar todas as APIs assíncronas e, idealmente, fluir o contexto. Como você está fazendo async, você pode simplesmente usar continuations ao invés de esperar para que nenhum deadlock seja causado, já que nenhuma espera é feita na biblioteca e você mantém o stream para que o contexto seja preservado (como HttpContext).

O problema é quando uma biblioteca expõe uma API síncrona, mas usa outra API assíncrona – portanto, é necessário usar Wait() / Result no seu código.

A maior desvantagem que encontrei com o uso de ConfigureAwait (false) é que a cultura de thread é revertida para o padrão do sistema. Se você configurou uma cultura, por exemplo …

   ... 

e você está hospedando em um servidor cuja cultura é definida como en-US, então você encontrará antes que ConfigureAwait (false) é chamado CultureInfo.CurrentCulture retornará en-AU e depois você receberá en-US. ou seja

 // CultureInfo.CurrentCulture ~ {en-AU} await xxxx.ConfigureAwait(false); // CultureInfo.CurrentCulture ~ {en-US} 

Se o seu aplicativo estiver fazendo qualquer coisa que exija formatação de dados específica da cultura, você precisará estar ciente disso ao usar ConfigureAwait (false).