Para que serve?
Antes de falar do tópico principal, vamos falar do famigerado tipo Task. 
Task-based asynchronous pattern e o tipo Task
A Task surgiu junto do Task-based asynchronous pattern (TAP)
, que simplifica a representação da inicialização e conclusão de uma operação assíncrona.
O TAP foi incluído no .NET Framework 4 e desde então é a forma recomendada de lidar com programação assíncrona e introduziu o namespace System.Threading.Tasks
 e, com ele, o tipo Task, cuja finalidade é representar uma operação assíncrona.
Uma Task representa uma operação que eventualmente produzirá um resultado, poderá falhar (lançar exceção) ou ser cancelada.
Ela é essencialmente um contêiner que descreve o andamento e o estado da operação assíncrona.
  
  
  O que vem depois do await?
Uma vez invocado um método que gera uma operação assíncrona, essa operação será executada de forma independente, e podemos usar o operador await para suspender a execução até que o resultado esteja disponível.
Vamos considerar o exemplo:
public async Task GetData(object params)
{
    var validParams = Validate(params);
    var data = await _service.DoSomething(validParams);
    UpdateUI(data);
}
No código acima, o compilador divide o método em duas partes: antes e depois do await.
Antes do await temos a parte síncrona, e depois do await temos a continuação (continuation).
Essa continuação pode ou não ser executada em outra thread, depende do contexto e do estado da Task.
Se a Task já estiver concluída, a continuação pode ocorrer na mesma thread; se não, pode ocorrer em uma thread diferente.
Durante a geração desse código, o compilador captura o contexto atual, que pode ser:
- Um UI Context (WPF, WinForms),
 - Um ASP.NET request context (ASP.NET clássico),
 - Ou simplesmente nenhum contexto, como em ASP.NET Core ou Console Apps.
 
Esse contexto é representado pelo objeto SynchronizationContext.
De forma simplificada, ele é responsável por orquestrar a execução assíncrona e decidir em qual thread a continuação do await será executada.
  
  
  Ok, e o .ConfigureAwait()?
É aqui que entra o nosso amigo ConfigureAwait(bool).
Esse método recebe um parâmetro booleano chamado continueOnCapturedContext, e indica se o código após o await deve ou não voltar ao contexto original capturado.
true (padrão): a continuação é executada de volta ao contexto original (por exemplo, a thread da UI).
false: a continuação é executada em qualquer thread disponível do ThreadPool, sem retornar ao contexto original.
Por padrão, o valor é true, ou seja, todo código após o await tenta continuar no mesmo contexto em que a Task foi chamada.
  
  
  .ConfigureAwait(false) sempre então! Certo?!
Você pode estar pensando:
“Se eu não importo que meu código seja continuado em outra thread, posso poupar o
SynchronizationContextdo trabalho e ganhar performance, não bloqueando a thread principal. Então vou usar.ConfigureAwait(false)sempre!”
Essa é uma conclusão comum, e equivocada.
De fato, muita gente passou a usar .ConfigureAwait(false) em todo lugar após ler recomendações como as de Stephen Cleary em Async/Await Best Practices.

No entanto, o uso indiscriminado pode causar bugs difíceis de rastrear, especialmente quando diferentes pessoas mantêm o código sem entender o motivo do uso.
Em muitos casos, remover um .ConfigureAwait(false) antigo resolve um bug misterioso, o tipo de problema “isso estava aqui desde 2010 ninguém entende o porquê”.
Além disso, não há ganho de performance perceptível ao usar false; o benefício está em evitar dependência do contexto (por exemplo, para evitar deadlocks em bibliotecas reutilizáveis).
O que pode dar errado?
Vamos supor que temos o código inicial, que roda em uma aplicação WinForms ou WPF, mas agora adicionamos o .ConfigureAwait(false):
public async Task GetData(object params)
{
    var validParams = Validate(params);
    var data = await _service.DoSomething(validParams)
                             .ConfigureAwait(false);
    UpdateUI(data);
}
Nesse caso, o UpdateUI(data) será executado em uma thread diferente da principal.
Como apenas a thread da UI pode atualizar elementos visuais, isso resultará na seguinte exceção:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
Ou seja, .ConfigureAwait(false) foi usado de forma incorreta.
O próprio Stephen Cleary destacou isso em uma resposta no StackOverflow.
Mesmo fora de aplicações gráficas, .ConfigureAwait(true) pode causar deadlocks, especialmente no ASP.NET Framework clássico, onde cada requisição possui seu próprio SynchronizationContext.
Exemplo:
public class RandomLibraryService
{
    public async Task<string> GetDataAsync()
    {
        // ⚠️ Usa o contexto original (continueOnCapturedContext = true)
        await Task.Delay(1000);
        return "Hello World!";
    }
}
// ASP.NET Framework, não Core
public class HomeController : Controller
{
    private RandomLibraryService _rls;
    public string Index()
    {
        // ⚠️ Chamando async de forma síncrona
        return _rls.GetDataAsync().Result;
    }
}
Aqui, a requisição HTTP é associada a um AspNetSynchronizationContext.
Quando Index() chama GetDataAsync().Result, a thread da requisição é bloqueada.
O método GetDataAsync() libera a thread e registra a continuação para o mesmo contexto capturado, que está bloqueado, gerando um deadlock.
Mesmo fora de ambientes de UI, isso ocorre porque o código tenta retornar ao contexto original, que está indisponível.
Em ambientes modernos como ASP.NET Core, isso não acontece, pois o SynchronizationContext foi removido.
  
  
  Quando usar .ConfigureAwait(false)?
✅ Em bibliotecas e pacotes reutilizáveis, pois você não controla o contexto do chamador.
Isso evita depender de umSynchronizationContextdesconhecido.⚠️ Em código de aplicação, normalmente não há necessidade de usá-lo.
O comportamento padrão (true) é mais previsível e seguro.
Stephen Toub (Microsoft) reforça isso no ConfigureAwait FAQ:
“If you’re writing app-level code, do not use ConfigureAwait(false).”
Conclusão
Quando await espera uma Task, o comportamento padrão é capturar um contexto, que pode ser um SynchronizationContext, ou na ausência dele, o TaskScheduler.Current.
Quando a Task termina, o código após o await é executado nesse contexto capturado (.ConfigureAwait(continueOnCapturedContext: true) é o comportamento padrão).
Usar .ConfigureAwait(false) instrui o runtime a não retornar ao contexto capturado, permitindo que o código continue em qualquer thread disponível do thread pool.
Isso não significa que o código após o await sempre rodará em outra thread, isso só acontece se o await realmente suspender a execução (ou seja, se a Task ainda não estiver concluída).
A dica final é, se não está desenvolvendo um pacote de uso geral, ou lidando com uma implementação customizada de SynchronizationContext, muito provável que não precise usar .ConfigureAwait().
Fontes:
- Async/Await - Best Practices in Asynchronous Programming (2013)
 - ASP.NET Core SynchronizationContext
 - ConfigureAwait FAQ (2019)
 - Pergunta do StackOverflow respondida pelo Stephen Cleary (2020)
 - How Async/Await Really Works in C# (2023)
 - .ConfigureAwait() in .NET 8 (2023)
 - LinkedIn Post: Should I Use ConfigureAwait true or false
 
    
Top comments (0)