Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Async/await simplifica a programação assíncrona, mas certos erros aparecem repetidamente. Este artigo descreve os cinco bugs mais comuns no código assíncrono e mostra como corrigir cada um deles.
O método assíncrono é executado de forma síncrona
Adicionar a async palavra-chave a um método não faz com que o método seja executado em um thread em segundo plano. Informa ao compilador que permita await dentro do corpo do método e envolva o valor de retorno em um Task. Quando você invoca um método assíncrono, ele é executado de forma síncrona até chegar ao primeiro await em um awaitable incompleto. Se o método não contiver expressões await ou se cada elemento awaitable já estiver concluído, o método será completado inteiramente no thread de chamada:
public static class SyncExecutionExample
{
public static Task<int> ComputeAsync()
{
// No await in this method — it runs entirely synchronously.
return Task.FromResult(42);
}
}
Public Module SyncExecutionExample
Public Function ComputeAsync() As Task(Of Integer)
' No Await in this method — it runs entirely synchronously.
Return Task.FromResult(42)
End Function
End Module
Aqui, o método retorna uma tarefa concluída imediatamente porque ela nunca cede. O compilador emite um aviso quando um método assíncrono não tem await expressões.
Se o seu objetivo for descarregar o trabalho associado à CPU em um thread do pool, use Run em vez de async:
public static class OffloadExample
{
public static int ComputeIntensive()
{
int sum = 0;
for (int i = 0; i < 1_000; i++)
sum += i;
return sum;
}
public static Task<int> ComputeOnThreadPoolAsync()
{
return Task.Run(() => ComputeIntensive());
}
}
Public Module OffloadExample
Public Function ComputeIntensive() As Integer
Dim sum As Integer = 0
For i As Integer = 0 To 999
sum += i
Next
Return sum
End Function
Public Function ComputeOnThreadPoolAsync() As Task(Of Integer)
Return Task.Run(Function() ComputeIntensive())
End Function
End Module
Para obter mais diretrizes sobre quando usar Task.Run, consulte wrappers assíncronos para métodos síncronos.
Não é possível aguardar um método assíncrono nulo
Quando você converte um método síncrono void-returning em assíncrono, altere o tipo de retorno para Task. Se você deixar o tipo de retorno como void, o método se tornará "nulo assíncrono", que você não pode aguardar:
public static class AsyncVoidExample
{
// BAD: async void — can't be awaited.
public static async void DoWorkBadAsync()
{
await Task.Delay(100);
}
// GOOD: async Task — callers can await this.
public static async Task DoWorkGoodAsync()
{
await Task.Delay(100);
}
}
Public Module AsyncVoidExample
' BAD: Async Sub — can't be awaited.
Public Async Sub DoWorkBadAsync()
Await Task.Delay(100)
End Sub
' GOOD: Async Function returning Task — callers can await this.
Public Async Function DoWorkGoodAsync() As Task
Await Task.Delay(100)
End Function
End Module
Os métodos assíncronos nulos servem a uma finalidade específica: manipuladores de eventos de nível superior em estruturas de interface do usuário. Fora dos manipuladores de eventos, sempre retorne Task ou Task<T> de métodos assíncronos. Os métodos assíncronos nulos têm estas desvantagens:
- As exceções passam despercebidas. As exceções geradas em um método nulo assíncrono se propagam para o SynchronizationContext que estava ativo quando o método foi iniciado. O chamador não pode capturar essas exceções.
- Os chamadores não podem acompanhar a conclusão. Sem um
Task, não há mecanismo para saber quando a operação é concluída. - O teste é difícil. Você não pode aguardar o método em um teste para verificar seu comportamento.
Deadlocks devido ao bloqueio no código assíncrono
Esse bug é a causa mais comum do código assíncrono que "nunca é concluído". Isso acontece quando você bloqueia de forma síncrona (chamada Wait, Task<TResult>.Result ou GetAwaiter.GetResult) em um thread que possui um SynchronizationContext de thread único.
A sequência que causa um deadlock:
- O código no thread da interface (ou um thread de solicitação ASP.NET no ASP.NET mais antigo) chama um método assíncrono e bloqueia a tarefa retornada.
- O método assíncrono aguarda uma tarefa incompleta sem usar
ConfigureAwait(false). - Quando a tarefa aguardada é concluída, a continuação tenta postar de volta no
SynchronizationContextoriginal. - O thread do contexto está bloqueado aguardando a conclusão da tarefa — situação de deadlock.
public static class DeadlockExample
{
public static async Task<string> GetDataAsync()
{
// Without ConfigureAwait(false), this continuation
// posts back to the original SynchronizationContext.
await Task.Delay(100);
return "data";
}
public static void CallerThatDeadlocks()
{
// On a single-threaded SynchronizationContext (e.g. UI thread),
// the following line deadlocks because the continuation needs
// the same thread that .Result is blocking.
string result = GetDataAsync().Result;
}
}
Public Module DeadlockExample
Public Async Function GetDataAsync() As Task(Of String)
' Without ConfigureAwait(False), this continuation
' posts back to the original SynchronizationContext.
Await Task.Delay(100)
Return "data"
End Function
Public Sub CallerThatDeadlocks()
' On a single-threaded SynchronizationContext (e.g. UI thread),
' the following line deadlocks because the continuation needs
' the same thread that .Result is blocking.
Dim result As String = GetDataAsync().Result
End Sub
End Module
Como evitar deadlocks
Use uma ou mais destas estratégias:
Não bloqueie. Use
awaitem vez de.Resultou.Wait():public static class DeadlockFix1 { public static async Task CallerFixedAsync() { // Use await instead of .Result string result = await DeadlockExample.GetDataAsync(); Console.WriteLine(result); } }Public Module DeadlockFix1 Public Async Function CallerFixedAsync() As Task ' Use Await instead of .Result Dim result As String = Await DeadlockExample.GetDataAsync() Console.WriteLine(result) End Function End ModuleUse
ConfigureAwait(false)no código da biblioteca. Quando o método de biblioteca não precisar ser retomado no contexto do chamador, especifiqueConfigureAwait(false)em cadaawait:public static class DeadlockFix2 { public static async Task<string> GetDataSafeAsync() { await Task.Delay(100).ConfigureAwait(false); return "data"; } }Public Module DeadlockFix2 Public Async Function GetDataSafeAsync() As Task(Of String) Await Task.Delay(100).ConfigureAwait(False) Return "data" End Function End ModuleUsar
ConfigureAwait(false)informa ao runtime para não realizar o marshaling da continuação de volta para oSynchronizationContextoriginal. Essa abordagem protege os chamadores que bloqueiam e melhora o desempenho evitando saltos de thread desnecessários.
Aviso
Deadlocks de construtor estático. O CLR mantém um bloqueio durante a execução de construtores estáticos (cctors). Se um construtor estático bloquear em relação a uma tarefa, e a continuação dessa tarefa precisar executar o código no mesmo tipo (ou em um tipo envolvido na cadeia de construção), ela não poderá prosseguir porque o bloqueio cctor está sendo mantido. Evite bloquear totalmente as chamadas dentro de construtores estáticos.
Tarefa<Desembrulhamento>
Quando você passa um lambda assíncrono para um método como StartNew, o objeto retornado é um Task<Task> (ou Task<Task<TResult>>), não um simples Task. A tarefa externa é concluída assim que o lambda assíncrono atinge seu primeiro rendimento await. Ele não aguarda a conclusão da tarefa interna:
public static class TaskTaskBugExample
{
public static async Task DemoAsync()
{
var sw = Stopwatch.StartNew();
// StartNew returns Task<Task>, not Task.
// The outer task completes immediately when the lambda yields.
await Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
});
// Elapsed shows ~0 seconds, not ~1 second.
Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s");
}
}
Public Module TaskTaskBugExample
Public Async Function DemoAsync() As Task
Dim sw = Stopwatch.StartNew()
' StartNew returns Task(Of Task), not Task.
' The outer task completes immediately when the lambda yields.
Await Task.Factory.StartNew(Async Function()
Await Task.Delay(1000)
End Function)
' Elapsed shows ~0 seconds, not ~1 second.
Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s")
End Function
End Module
Corrija esse problema de uma das três maneiras:
Use Run em seu lugar.
Task.Rundesempacota automaticamenteTask<Task>:public static class TaskTaskFix1 { public static async Task DemoAsync() { var sw = Stopwatch.StartNew(); await Task.Run(async () => { await Task.Delay(1000); }); Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s"); } }Public Module TaskTaskFix1 Public Async Function DemoAsync() As Task Dim sw = Stopwatch.StartNew() Await Task.Run(Async Function() Await Task.Delay(1000) End Function) Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s") End Function End ModuleChame Unwrap o resultado:
public static class TaskTaskFix2 { public static async Task DemoAsync() { var sw = Stopwatch.StartNew(); await Task.Factory.StartNew(async () => { await Task.Delay(1000); }).Unwrap(); Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s"); } }Public Module TaskTaskFix2 Public Async Function DemoAsync() As Task Dim sw = Stopwatch.StartNew() Await Task.Factory.StartNew(Async Function() Await Task.Delay(1000) End Function).Unwrap() Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s") End Function End ModuleAguarde duas vezes (primeiro a tarefa externa, depois a parte interna):
public static class TaskTaskFix3 { public static async Task DemoAsync() { var sw = Stopwatch.StartNew(); Task<Task> outerTask = Task.Factory.StartNew(async () => { await Task.Delay(1000); }); Task innerTask = await outerTask; await innerTask; Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s"); } }Public Module TaskTaskFix3 Public Async Function DemoAsync() As Task Dim sw = Stopwatch.StartNew() Dim outerTask As Task(Of Task) = Task.Factory.StartNew(Async Function() Await Task.Delay(1000) End Function) Dim innerTask As Task = Await outerTask Await innerTask Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s") End Function End Module
Espera ausente em uma chamada de retorno de tarefa
Se você chamar um método que retorna uma tarefa em um método async sem esperar, o método iniciará a operação assíncrona, mas não aguardará a conclusão. O compilador avisa sobre esse caso com CS4014 em C# e BC42358 no Visual Basic:
public static class MissingAwaitExample
{
// BAD: Task.Delay is started but never awaited.
public static async Task PauseOneSecondBuggyAsync()
{
Task.Delay(1000); // CS4014 warning
}
// GOOD: await the task.
public static async Task PauseOneSecondAsync()
{
await Task.Delay(1000);
}
}
Public Module MissingAwaitExample
' BAD: Task.Delay is started but never awaited.
Public Async Function PauseOneSecondBuggyAsync() As Task
Task.Delay(1000) ' Warning BC42358
End Function
' GOOD: Await the task.
Public Async Function PauseOneSecondAsync() As Task
Await Task.Delay(1000)
End Function
End Module
Armazenar o resultado em uma variável suprime o aviso, mas não corrige o bug subjacente. Sempre await a tarefa, a menos que você queira intencionalmente um comportamento de disparar e esquecer.