//JorgenHoc
← Todos os artigos
Async C#Por Jorge CalderónAtualizado 17 min read

ConfigureAwait(false) em C# — A Explicação Completa

Entenda exatamente o que ConfigureAwait(false) faz — medido em chamadas a SynchronizationContext.Post — quando você precisa dele, quando pode omiti-lo, e a sobrecarga ConfigureAwaitOptions do .NET 8.

#csharp#async#dotnet

Todo desenvolvedor C# já viu ConfigureAwait(false) espalhado por código de bibliotecas e se perguntou se deve copiar esse padrão. A resposta curta depende inteiramente do tipo de código que você está escrevendo e do ambiente de execução que o hospeda.

O que é SynchronizationContext?

SynchronizationContext é uma abstração que permite que o runtime do .NET saiba onde publicar uma continuação após a conclusão de um await. Pense nele como um agendador que diz "quando esta operação assíncrona terminar, retome a execução aqui."

Diferentes modelos de aplicação instalam contextos diferentes:

Tipo de aplicaçãoSynchronizationContext presente?O que faz
WinFormsSim (WindowsFormsSynchronizationContext)Direciona continuações de volta à thread de UI
WPFSim (DispatcherSynchronizationContext)Idem — direciona à thread do Dispatcher
ASP.NET (clássico, .NET Framework)Sim (AspNetSynchronizationContext)Garante que apenas uma thread execute por requisição
ASP.NET CoreNãoSem contexto; threads do pool são usadas diretamente
Aplicação console (.NET 5+)NãoSem contexto; o thread pool é usado
Blazor WebAssemblySimSingle-threaded; contexto direciona de volta à thread do JS
xUnit / NUnit / MSTestVaria por versãoAlguns instalam contexto para sincronização em testes

A presença ou ausência de um SynchronizationContext é o fato-chave que orienta cada decisão neste artigo.

ConfigureAwait(true) — O Comportamento Padrão

Quando você escreve um await simples, o C# o compila como await someTask.ConfigureAwait(true). O argumento true significa:

  1. Antes de suspender, capturar o SynchronizationContext atual (ou TaskScheduler se não houver contexto).
  2. Quando a tarefa aguardada concluir, publicar a continuação de volta para esse contexto capturado.

Isso é o que torna o código de UI seguro — você pode fazer await de uma chamada de rede e então tocar em um TextBox na linha seguinte sem um Dispatcher.Invoke explícito:

// Code-behind do WPF — ConfigureAwait(true) é o padrão
private async void Button_Click(object sender, RoutedEventArgs e)
{
    string result = await FetchDataAsync(); // suspende aqui
    // Retoma na thread de UI — seguro para tocar controles
    myLabel.Content = result;
}

O custo: após a tarefa concluir em uma thread do pool, o runtime precisa publicar a continuação de volta ao contexto capturado (por exemplo, o loop de mensagens da UI). Essa ida e volta tem um custo, e em frameworks como o ASP.NET clássico pode causar deadlocks.

ConfigureAwait(false) — Ignorando a Recaptura do Contexto

ConfigureAwait(false) diz ao awaiter: "Não preciso retomar no contexto original. Retome em qualquer thread disponível (normalmente uma thread do pool)."

string result = await FetchDataAsync().ConfigureAwait(false);
// Retoma em uma thread do pool — NÃO tocar controles de UI aqui

Internamente, a máquina de estados gerada pelo compilador verifica o sinalizador ContinueOnCapturedContext do awaiter. Quando é false, a continuação é agendada diretamente no thread pool (via ThreadPool.QueueUserWorkItem ou similar), em vez de ser publicada no SynchronizationContext capturado.

Comparação Passo a Passo

// --- Fluxo com ConfigureAwait(true) ---
// Thread: thread de UI (SynchronizationContext = DispatcherSynchronizationContext)
await Task.Delay(100);
// 1. DispatcherSynchronizationContext é capturado
// 2. Task.Delay conclui na thread do pool T2
// 3. T2 publica a continuação na fila do Dispatcher
// 4. Thread de UI a recolhe da fila
// Thread: thread de UI novamente ✓
 
// --- Fluxo com ConfigureAwait(false) ---
// Thread: thread de UI (SynchronizationContext = DispatcherSynchronizationContext)
await Task.Delay(100).ConfigureAwait(false);
// 1. SynchronizationContext intencionalmente NÃO é capturado
// 2. Task.Delay conclui na thread do pool T2
// 3. Continuação executa diretamente em T2 (sem ida e volta ao Dispatcher)
// Thread: thread do pool T2

Medido: Contando os Posts

Essa descrição do fluxo é verificável, porque "publicar a continuação de volta" é uma chamada a SynchronizationContext.Post. samples/configureawait-false-csharp instala um contexto de thread única estilo UI com um contador em Post e executa o mesmo método das duas formas — números determinísticos, idênticos em qualquer máquina, ao contrário de tempos em nanossegundos:

Cenário (na thread do contexto)AwaitsPosts contadosTambém verificado
Awaits simples55o contexto sobrevive; ainda na thread estilo UI
Todos com ConfigureAwait(false)50Current é null após o primeiro await; fora da thread de UI
ConfigureAwait(false) só no primeiro await1 + 4 simples0a regra de propagação abaixo
Sem contexto (thread de console)2n/aas duas formas se comportam idêntico — o caso do ASP.NET Core

Um await, um Post; ConfigureAwait(false) pula exatamente isso. Todo o resto deste artigo deriva desses números.

Saída de console do sample: cinco awaits simples produzem cinco Posts de volta ao contexto e o SynchronizationContext sobrevive a cada await; os mesmos cinco awaits com ConfigureAwait(false) produzem zero Posts e Current passa a ser null; ConfigureAwait(false) só no primeiro await ainda resulta em zero Posts no total; uma thread de console sem contexto se comporta idêntico das duas formas; e uma exceção depois de ConfigureAwait(false) é capturada normalmente. Todas as verificações passaram.
A execução do sample: cada afirmação verificada, terminando com o resumo de uma linha — ConfigureAwait(false) faz uma só coisa: pula o Post.

O Deadlock Clássico — Por Que ConfigureAwait(false) Importa

A razão mais importante para conhecer essa API é a prevenção de deadlocks em padrões de bloqueio sobre código assíncrono. Considere o ASP.NET clássico (.NET Framework), onde um SynchronizationContext limita a concorrência a uma thread por requisição:

// Método de biblioteca — NÃO usa ConfigureAwait(false)
public async Task<string> GetDataAsync()
{
    await Task.Delay(500); // captura AspNetSynchronizationContext
    return "hello";
}
 
// Action do controlador — bloqueia de forma síncrona (má prática, mas acontece)
public ActionResult Index()
{
    // .Result bloqueia a thread da requisição enquanto segura o bloqueio do contexto
    string data = GetDataAsync().Result; // DEADLOCK
    return Content(data);
}

Por que ocorre o deadlock:

  1. Index() chama GetDataAsync() e bloqueia a thread da requisição com .Result.
  2. A thread da requisição retém o AspNetSynchronizationContext.
  3. Task.Delay conclui e tenta publicar a continuação de volta para esse contexto.
  4. O contexto está bloqueado (a thread da requisição está bloqueada em .Result).
  5. A continuação aguarda o contexto. A thread bloqueada aguarda a continuação. Deadlock.

Solução com ConfigureAwait(false):

public async Task<string> GetDataAsync()
{
    // NÃO tenta retornar ao AspNetSynchronizationContext
    await Task.Delay(500).ConfigureAwait(false);
    return "hello"; // executa em uma thread do pool — sem contexto necessário
}

Agora, quando Task.Delay conclui, a continuação é executada em uma thread do pool em vez de tentar re-entrar no contexto retido. O deadlock é quebrado.

⚠️

O deadlock só ocorre quando alguém chama .Result, .Wait(), ou GetAwaiter().GetResult() em um método assíncrono que usa o ConfigureAwait(true) padrão. A solução real é tornar toda a cadeia de chamadas assíncrona. ConfigureAwait(false) é uma rede de segurança, não uma licença para bloquear.

Deadlock no WinForms / WPF — O Mesmo Padrão

// WPF: handler de clique que chama .Result (nunca faça isso em código real)
private void BadButton_Click(object sender, RoutedEventArgs e)
{
    // Thread de UI bloqueia, retém DispatcherSynchronizationContext
    string result = SomeLibraryMethod().Result; // deadlock se a biblioteca usa ConfigureAwait(true)
    myLabel.Content = result;
}
 
// Biblioteca — correto: usa ConfigureAwait(false) em todo lugar
public async Task<string> SomeLibraryMethod()
{
    var data = await HttpClient.GetStringAsync("https://example.com")
        .ConfigureAwait(false); // não tentará publicar de volta ao Dispatcher
    return data.ToUpper();
}

Quando ConfigureAwait(false) É Irrelevante

ASP.NET Core

O ASP.NET Core deliberadamente não tem SynchronizationContext. Quando não há contexto para capturar, ConfigureAwait(true) e ConfigureAwait(false) se comportam de forma idêntica — ambos retomam em uma thread do pool.

// Controlador do ASP.NET Core — ConfigureAwait(false) não tem efeito aqui
[HttpGet("data")]
public async Task<IActionResult> GetData()
{
    // Comportamento idêntico com ou sem ConfigureAwait(false)
    var result = await _service.FetchAsync();
    return Ok(result);
}

Aplicações Console (.NET 5+)

Aplicações console também não têm SynchronizationContext. Qualquer await já retoma no thread pool. Adicionar ConfigureAwait(false) é uma operação nula.

static async Task Main(string[] args)
{
    // Sem SynchronizationContext — ConfigureAwait(false) é redundante
    var data = await File.ReadAllTextAsync("input.txt").ConfigureAwait(false);
    Console.WriteLine(data);
}

Por Que Código de Biblioteca Deve Sempre Usar ConfigureAwait(false)

O autor de uma biblioteca não pode saber quem chamará seu código. O chamador pode ser:

  • Uma aplicação WPF com DispatcherSynchronizationContext
  • Uma aplicação ASP.NET clássica com AspNetSynchronizationContext
  • Uma aplicação Blazor com seu próprio contexto
  • Um executor de testes unitários que instala um contexto personalizado

Se a biblioteca usa ConfigureAwait(true) (o padrão) e o chamador bloqueia em .Result, o cenário de deadlock se torna possível. Se a biblioteca usa ConfigureAwait(false), ela opta por sair completamente do contexto do chamador e não pode contribuir para o deadlock.

// Bom código de biblioteca — agnóstico ao contexto em todo lugar
public class DataClient
{
    private readonly HttpClient _http;
 
    public DataClient(HttpClient http) => _http = http;
 
    public async Task<UserDto> GetUserAsync(int id)
    {
        // ConfigureAwait(false) em cada await — a biblioteca não possui o contexto
        var response = await _http.GetAsync($"/users/{id}").ConfigureAwait(false);
        response.EnsureSuccessStatusCode();
 
        var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        return JsonSerializer.Deserialize<UserDto>(json)!;
    }
 
    public async Task<IReadOnlyList<OrderDto>> GetOrdersAsync(int userId)
    {
        var response = await _http.GetAsync($"/users/{userId}/orders").ConfigureAwait(false);
        response.EnsureSuccessStatusCode();
 
        await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
        var orders = await JsonSerializer.DeserializeAsync<List<OrderDto>>(stream).ConfigureAwait(false);
        return orders ?? [];
    }
}
💡

A regra geral: se você está escrevendo um pacote NuGet ou biblioteca compartilhada, adicione ConfigureAwait(false) a cada await. Se está escrevendo código de aplicação voltado ao ASP.NET Core ou aplicação console, é opcional e adiciona ruído visual sem nenhum benefício.

A Regra de Propagação de Contexto

Uma vez que você usa ConfigureAwait(false) em um único await dentro de um método, todos os awaits subsequentes nesse mesmo método também podem executar sem o contexto, independentemente de sua própria configuração de ConfigureAwait. Isso ocorre porque SynchronizationContext.Current se torna null na thread do pool onde a execução continua.

public async Task ExampleAsync()
{
    // Ainda no contexto original
    await FirstOperation().ConfigureAwait(false);
 
    // Agora na thread do pool — SynchronizationContext.Current é null
    // Este ConfigureAwait(true) é efetivamente igual a ConfigureAwait(false)
    // porque não há contexto para retornar
    await SecondOperation().ConfigureAwait(true); // redundante — sem contexto presente
    await ThirdOperation(); // também está correto — sem contexto para capturar
}

Isso não significa que você deva ser inconsistente. Escreva ConfigureAwait(false) em cada await no código de biblioteca para ser explícito e evitar confusão.

O sample verifica essa regra por contagem: um ConfigureAwait(false) seguido de quatro awaits simples produziu zero Posts — os awaits simples já não tinham contexto a capturar.

Analisadores Roslyn

Adicionar manualmente ConfigureAwait(false) a cada await é tedioso e sujeito a erros. Vários analisadores Roslyn aplicam isso automaticamente.

Microsoft.VisualStudio.Threading.Analyzers

Instale via NuGet:

dotnet add package Microsoft.VisualStudio.Threading.Analyzers

Este pacote fornece a regra VSTHRD111 (UseConfigureAwait) que avisa sobre qualquer await que não tenha ConfigureAwait:

// Aviso VSTHRD111: Use .ConfigureAwait(false) ao aguardar uma tarefa
var result = await SomeTaskAsync(); // ⚠ VSTHRD111

Configure no .editorconfig para tratá-lo como erro em projetos de biblioteca:

[*.cs]
dotnet_diagnostic.VSTHRD111.severity = error

Roslynator

dotnet add package Roslynator.Analyzers

A regra RCS1090 (UseConfigureAwaitFalse) fornece aplicação similar.

Abordagem com .editorconfig

Você também pode configurar o analisador CA2007 integrado (disponível nos analisadores do SDK do .NET):

[*.cs]
# CA2007: Considere chamar ConfigureAwait na tarefa aguardada
dotnet_diagnostic.CA2007.severity = warning

Nota: CA2007 só é acionado ao compilar com <EnableNETAnalyzers>true</EnableNETAnalyzers>, que é o padrão para novos projetos no estilo SDK voltados ao .NET 5+.

.NET 8: ConfigureAwaitOptions

Primeiro, desfazendo um mito que este próprio artigo costumava repetir: não existe um padrão de ConfigureAwait(false) em nível de projeto no .NET — nem propriedade de MSBuild, nem RuntimeHostConfigurationOption, nem atributo em nível de assembly. Uma versão anterior deste artigo descrevia tal mecanismo; ele não existe — o ConfigureAwait FAQ de Stephen Toub explica diretamente por que a equipe recusou repetidamente adicionar um interruptor global. A única alavanca em nível de projeto continua sendo a aplicação com analisadores (abaixo).

O que o .NET 8 realmente adicionou é uma sobrecarga: ConfigureAwait(ConfigureAwaitOptions), um enum de flags que generaliza o booleano:

// ConfigureAwaitOptions.None == ConfigureAwait(false)
await task.ConfigureAwait(ConfigureAwaitOptions.None);
 
// ConfigureAwaitOptions.ContinueOnCapturedContext == ConfigureAwait(true)
await task.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext);
 
// Novo: conclui o await mesmo que a task tenha falhado ou sido cancelada — não
// relança a exceção. Só é válido em Task não genérica (uma Task<T> não teria
// resultado para te devolver).
await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
 
// Novo: sempre cede o controle, mesmo que a task já tenha concluído — o await nunca
// continua sincronamente. Útil para forçar justiça ou sair de um caminho com lock.
await task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
 
// São flags, então se combinam:
await task.ConfigureAwait(
    ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ForceYielding);

A sobrecarga existe em Task e Task<TResult> (não em ValueTask no .NET 8), e SuppressThrowing em uma Task<TResult> lança ArgumentOutOfRangeException na chamada ao ConfigureAwait — o uso incorreto falha rápido em vez de devolver silenciosamente um resultado padrão.

A Verdadeira Solução em Nível de Projeto: Aplicar com Analisadores

Como não existe um padrão, a abordagem prática para todo o repositório é transformar a regra do analisador em erro no Directory.Build.props:

<!-- Directory.Build.props — aplica a todos os projetos na árvore de diretório -->
<Project>
  <PropertyGroup>
    <!-- Tratar ConfigureAwait faltante como erro de compilação em todo o repositório -->
    <WarningsAsErrors>$(WarningsAsErrors);CA2007</WarningsAsErrors>
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
    <AnalysisMode>All</AnalysisMode>
  </PropertyGroup>
</Project>

Equívocos Comuns

Equívoco 1: ConfigureAwait(false) Melhora a Performance

A medição acima dá forma precisa a isso: o que ConfigureAwait(false) economiza é um Post por await — e no ASP.NET Core ou em uma app de console, zero, porque não há contexto para o qual publicar. Um post de contexto é barato; a razão para usar ConfigureAwait(false) é blindar código de biblioteca contra deadlocks, não velocidade. Não o adicione ao código de aplicação por performance — o ruído que adiciona supera qualquer ganho teórico.

// NÃO faça isso por "performance" no ASP.NET Core — adiciona ruído sem benefício
public async Task<string> GetDataAsync()
{
    return await _cache.GetAsync("key").ConfigureAwait(false); // inútil no ASP.NET Core
}

Equívoco 2: ConfigureAwait(false) Afeta o Tratamento de Erros

A propagação de exceções funciona de forma idêntica independentemente do ConfigureAwait. Exceções lançadas dentro de uma tarefa aguardada ainda são capturadas e relançadas no ponto await, independentemente de qual thread executa essa continuação. (A última verificação do sample confirma isso: um método que lança depois de um await com ConfigureAwait(false) é capturado por um try/catch comum no ponto de chamada. A única exceção à regra no .NET 8 é opcional: ConfigureAwaitOptions.SuppressThrowing, coberta acima.)

public async Task DemonstrateExceptionAsync()
{
    try
    {
        // Exceção lançada dentro de FetchAsync é relançada aqui,
        // independentemente de ConfigureAwait
        await FetchAsync().ConfigureAwait(false);
    }
    catch (HttpRequestException ex)
    {
        // Ainda é capturada — ConfigureAwait não afeta o fluxo de exceções
        _logger.LogError(ex, "Fetch failed");
        throw;
    }
}

Equívoco 3: ConfigureAwait(false) Torna o Código Thread-Safe

ConfigureAwait(false) apenas muda em qual thread a continuação é executada. Não tem efeito sobre thread safety, estado compartilhado ou condições de corrida. Você ainda precisa de primitivas de sincronização adequadas ao acessar estado mutável compartilhado.

Equívoco 4: Você Precisa de ConfigureAwait(false) em Métodos async void

Métodos async void são handlers de eventos e cenários de disparar-e-esquecer. Eles ainda capturam o SynchronizationContext e ainda se beneficiam de ConfigureAwait(false) se chamam código de biblioteca que pode estar bloqueado. Mas as mesmas regras se aplicam — você só precisa dele para prevenir deadlocks ou evitar trocas de contexto desnecessárias em frameworks que têm um contexto.

Padrões Práticos

Padrão 1: Biblioteca Wrapper de HttpClient

public class WeatherApiClient
{
    private readonly HttpClient _http;
    private readonly ILogger<WeatherApiClient> _logger;
 
    public WeatherApiClient(HttpClient http, ILogger<WeatherApiClient> logger)
    {
        _http = http;
        _logger = logger;
    }
 
    public async Task<WeatherForecast?> GetForecastAsync(
        string city,
        CancellationToken cancellationToken = default)
    {
        // Código de biblioteca: ConfigureAwait(false) em cada await
        var url = $"/api/weather?city={Uri.EscapeDataString(city)}";
 
        using var response = await _http
            .GetAsync(url, cancellationToken)
            .ConfigureAwait(false);
 
        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("Weather API returned {StatusCode}", response.StatusCode);
            return null;
        }
 
        await using var stream = await response.Content
            .ReadAsStreamAsync(cancellationToken)
            .ConfigureAwait(false);
 
        return await JsonSerializer
            .DeserializeAsync<WeatherForecast>(stream, cancellationToken: cancellationToken)
            .ConfigureAwait(false);
    }
}

Padrão 2: ViewModel do WPF — Onde NÃO Usar ConfigureAwait(false)

public class MainViewModel : INotifyPropertyChanged
{
    private string _status = string.Empty;
    public string Status
    {
        get => _status;
        set { _status = value; OnPropertyChanged(); }
    }
 
    public async Task LoadDataAsync()
    {
        // Await simples, deliberadamente: este método define estado vinculado à UI
        // depois, então precisa voltar à thread de UI. Os ConfigureAwait(false) vão
        // DENTRO de _apiClient (código de biblioteca) — a fronteira entre "I/O sem
        // contexto" e "atualização de UI com contexto" é a fronteira da API,
        // não uma linha deste método.
        var data = await _apiClient.GetForecastAsync("London");
 
        // Retomado na thread de UI — seguro tocar estado vinculado
        Status = data?.Summary ?? "Sem dados";
    }
}
⚠️

Um padrão que você encontrará por aí (e em uma versão anterior deste artigo): chamar TaskScheduler.FromCurrentSynchronizationContext() depois de um await ... ConfigureAwait(false) para "voltar" à UI. Isso lança InvalidOperationException — depois de ConfigureAwait(false) não há SynchronizationContext atual para capturar (o demo 2 do sample verifica exatamente isso: Current é null). Se um método precisa da thread de UI depois dos seus awaits, use await simples nesse método e empurre ConfigureAwait(false) para a biblioteca que ele chama.

Padrão 3: Repositório do Entity Framework Core

public class UserRepository
{
    private readonly AppDbContext _context;
 
    public UserRepository(AppDbContext context) => _context = context;
 
    public async Task<User?> GetByIdAsync(int id, CancellationToken ct = default)
    {
        // EF Core suporta ConfigureAwait(false) — seguro em código de biblioteca
        return await _context.Users
            .AsNoTracking()
            .FirstOrDefaultAsync(u => u.Id == id, ct)
            .ConfigureAwait(false);
    }
 
    public async Task<int> CreateUserAsync(User user, CancellationToken ct = default)
    {
        _context.Users.Add(user);
        await _context.SaveChangesAsync(ct).ConfigureAwait(false);
        return user.Id;
    }
}

Tabela de Decisão

CenárioUsar ConfigureAwait(false)?Motivo
Pacote NuGet / biblioteca de classes compartilhadaSim — sempreO contexto do chamador é desconhecido; previne deadlocks
Controlador/serviço do ASP.NET CoreNão (opcional)Sem SynchronizationContext presente
Aplicação console (.NET 5+)Não (opcional)Sem SynchronizationContext presente
WPF / WinForms — camada de UIDependeUse false para trabalho de I/O; omita ao atualizar estado de UI
WPF / WinForms — I/O no ViewModelSimI/O não deve reter o contexto de UI
ASP.NET clássico (.NET Framework)Sim — sempreRisco de deadlock com AspNetSynchronizationContext
Blazor WebAssemblySimSingle-threaded; evita sobrecarga de contexto
Testes unitários (qualquer executor)Não (opcional)Varia; geralmente seguro sem ele no .NET 5+
Handlers de eventos async voidNãoDevem retomar na thread de UI para atualizar controles

Resumo

ConfigureAwait(false) faz uma única coisa: diz ao awaiter para não capturar e restaurar o SynchronizationContext atual. As consequências são:

  • Continuações executam no thread pool em vez de serem publicadas de volta ao contexto original.
  • Deadlocks são prevenidos em frameworks que têm um contexto e onde alguém está bloqueando na tarefa com .Result ou .Wait().
  • Impacto na performance é insignificante — não use como otimização.
  • O tratamento de erros não é afetado — exceções se propagam exatamente da mesma forma.

A regra prática é simples: código de biblioteca usa ConfigureAwait(false) em cada await; código de aplicação ASP.NET Core e console não precisa. Use os analisadores Roslyn CA2007 ou VSTHRD111 para aplicar a regra automaticamente em toda uma base de código.

Leituras adicionais

Sobre o autor

Jorge Calderón

Engenheiro de software com mais de uma década construindo e operando aplicações .NET em produção — camadas de dados com EF Core, serviços intensivos em async e implantações em Azure e contêineres. Todos os benchmarks e projetos de exemplo destes guias estão publicados em um repositório público no GitHub para que você possa reproduzi-los.

Perfil no GitHubLinkedIn ↗Benchmarks e código de exemplo

Artigos relacionados