Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Os métodos assíncronos podem ter os seguintes tipos de retorno:
- Task, para um método assíncrono que executa uma operação, mas não retorna nenhum valor.
- Task<TResult>, para um método assíncrono que retorna um valor.
-
void
, para um manipulador de eventos. - Qualquer tipo que tenha um método
GetAwaiter
acessível. O objeto retornado pelo métodoGetAwaiter
deve implementar a interface System.Runtime.CompilerServices.ICriticalNotifyCompletion. - IAsyncEnumerable<T>, para um método assíncrono que retorna um fluxo assíncrono .
Para obter mais informações sobre métodos assíncronos, consulte Programação assíncrona com async and await (C#).
Existem também vários outros tipos específicos para cargas de trabalho do Windows:
- DispatcherOperation, para operações assíncronas limitadas ao Windows.
- IAsyncAction, para ações assíncronas em aplicativos da Plataforma Universal do Windows (UWP) que não retornam um valor.
- IAsyncActionWithProgress<TProgress>, para ações assíncronas em aplicativos UWP que relatam o progresso, mas não retornam um valor.
- IAsyncOperation<TResult>, para operações assíncronas em aplicativos UWP que retornam um valor.
- IAsyncOperationWithProgress<TResult,TProgress>, para operações assíncronas em aplicativos UWP que relatam o progresso e retornam um valor.
Tipo de retorno da tarefa
Os métodos assíncronos que não contêm uma instrução return
ou que contêm uma instrução return
que não retorna um operando geralmente têm um tipo de retorno de Task. Esses métodos retornam void
se forem executados de forma síncrona. Se você usar um tipo de retorno Task para um método assíncrono, um método de chamada poderá usar um operador de await
para suspender a conclusão do chamador até que o método assíncrono chamado seja concluído.
No exemplo a seguir, o método WaitAndApologizeAsync
não contém uma instrução return
, portanto, o método retorna um objeto Task. Retornar um Task
permite aguardar um WaitAndApologizeAsync
. O tipo Task não inclui uma propriedade Result
porque não tem valor de retorno.
public static async Task DisplayCurrentInfoAsync()
{
await WaitAndApologizeAsync();
Console.WriteLine($"Today is {DateTime.Now:D}");
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Console.WriteLine("The current temperature is 76 degrees.");
}
static async Task WaitAndApologizeAsync()
{
await Task.Delay(2000);
Console.WriteLine("Sorry for the delay...\n");
}
// Example output:
// Sorry for the delay...
//
// Today is Monday, August 17, 2020
// The current time is 12:59:24.2183304
// The current temperature is 76 degrees.
WaitAndApologizeAsync
é aguardado usando uma instrução await em vez de uma expressão await, semelhante à chamada para um método síncrono que retorna void. A aplicação de um operador await neste caso não produz um valor. Quando o operando direito de um await
é um Task<TResult>, a expressão await
produz um resultado de T
. Quando o operando direito de um await
é um Task, o await
e o seu operando são uma instrução.
Você pode separar a chamada para WaitAndApologizeAsync
do uso de um operador await, como mostra o código a seguir. No entanto, lembre-se de que um Task
não possui uma propriedade Result
e que nenhum valor é produzido quando um operador await é aplicado a um Task
.
O código a seguir separa a chamada do método WaitAndApologizeAsync
da espera pela tarefa que o método retorna.
Task waitAndApologizeTask = WaitAndApologizeAsync();
string output =
$"Today is {DateTime.Now:D}\n" +
$"The current time is {DateTime.Now.TimeOfDay:t}\n" +
"The current temperature is 76 degrees.\n";
await waitAndApologizeTask;
Console.WriteLine(output);
Tarefa<TResult tipo de retorno>
O tipo de retorno
No exemplo a seguir, o método GetLeisureHoursAsync
contém uma instrução return
que retorna um inteiro. A declaração de método deve especificar um tipo de retorno de Task<int>
. O método assíncrono FromResult serve de espaço reservado para uma operação que retorna um DayOfWeek.
public static async Task ShowTodaysInfoAsync()
{
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await GetLeisureHoursAsync()}";
Console.WriteLine(message);
}
static async Task<int> GetLeisureHoursAsync()
{
DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);
int leisureHours =
today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
? 16 : 5;
return leisureHours;
}
// Example output:
// Today is Wednesday, May 24, 2017
// Today's hours of leisure: 5
Quando GetLeisureHoursAsync
é chamado de dentro de uma expressão await no método ShowTodaysInfo
, a expressão await recupera o valor inteiro (o valor de leisureHours
) armazenado na tarefa retornada pelo método GetLeisureHours
. Para obter mais informações sobre expressões aguardar, consulte aguardar.
Você pode entender melhor como await
recupera o resultado de um Task<T>
separando a chamada para GetLeisureHoursAsync
do aplicativo de await
, como mostra o código a seguir. Uma chamada para o método GetLeisureHoursAsync
, caso não seja aguardada de imediato, devolve um Task<int>
, como você esperaria da declaração do método. A tarefa é atribuída à variável getLeisureHoursTask
no exemplo. Como getLeisureHoursTask
é um Task<TResult>, ele contém uma propriedade Result do tipo TResult
. Nesse caso, TResult
representa um tipo inteiro. Quando await
é aplicado a getLeisureHoursTask
, a expressão await é avaliada para obter o conteúdo da propriedade Result de getLeisureHoursTask
. O valor é atribuído à variável ret
.
Importante
A propriedade Result é uma propriedade de bloqueio. Se você tentar acessá-lo antes que sua tarefa seja concluída, o thread que está ativo no momento será bloqueado até que a tarefa seja concluída e o valor esteja disponível. Na maioria dos casos, você deve acessar o valor usando await
em vez de acessar a propriedade diretamente.
O exemplo anterior recuperou o valor da propriedade Result para bloquear o thread principal para que o método Main
pudesse imprimir o message
no console antes que o aplicativo terminasse.
var getLeisureHoursTask = GetLeisureHoursAsync();
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await getLeisureHoursTask}";
Console.WriteLine(message);
Tipo de retorno nulo
Você usa o tipo de retorno void
em manipuladores de eventos assíncronos, que exigem um tipo de retorno void
. Para métodos que não sejam manipuladores de eventos e que não retornem um valor, deverás retornar um Task em vez disso, porque um método assíncrono que retorna void
não pode ser aguardado. Qualquer chamador de tal método deve prosseguir até à conclusão sem esperar pelo término do método assíncrono chamado. O chamador deve ser independente de quaisquer valores ou exceções que o método assíncrono gera.
O chamador de um método assíncrono que retorna void não pode tratar as exceções lançadas pelo método. Essas exceções não tratadas provavelmente farão com que seu aplicativo falhe. Se um método que retorna um Task ou Task<TResult> gera uma exceção, a exceção é armazenada na tarefa retornada. A exceção é novamente lançada quando se aguarda a tarefa. Certifique-se de que qualquer método assíncrono que possa produzir uma exceção tenha um tipo de retorno de Task ou Task<TResult> e que as chamadas para o método sejam aguardadas.
O exemplo a seguir mostra o comportamento de um manipulador de eventos assíncrono. No código de exemplo, um manipulador de eventos assíncrono deve informar o thread principal quando ele terminar. Em seguida, o thread principal pode aguardar a conclusão de um manipulador de eventos assíncrono antes de sair do programa.
public class NaiveButton
{
public event EventHandler? Clicked;
public void Click()
{
Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
Clicked?.Invoke(this, EventArgs.Empty);
Console.WriteLine("All listeners are notified.");
}
}
public class AsyncVoidExample
{
static readonly TaskCompletionSource<bool> s_tcs = new TaskCompletionSource<bool>();
public static async Task MultipleEventHandlersAsync()
{
Task<bool> secondHandlerFinished = s_tcs.Task;
var button = new NaiveButton();
button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;
Console.WriteLine("Before button.Click() is called...");
button.Click();
Console.WriteLine("After button.Click() is called...");
await secondHandlerFinished;
}
private static void OnButtonClicked1(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 1 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 1 is done.");
}
private static async void OnButtonClicked2Async(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 2 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 2 is about to go async...");
await Task.Delay(500);
Console.WriteLine(" Handler 2 is done.");
s_tcs.SetResult(true);
}
private static void OnButtonClicked3(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 3 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 3 is done.");
}
}
// Example output:
//
// Before button.Click() is called...
// Somebody has clicked a button. Let's raise the event...
// Handler 1 is starting...
// Handler 1 is done.
// Handler 2 is starting...
// Handler 2 is about to go async...
// Handler 3 is starting...
// Handler 3 is done.
// All listeners are notified.
// After button.Click() is called...
// Handler 2 is done.
Tipos de retorno assíncrono generalizados e ValueTask<TResult>
Um método assíncrono pode retornar qualquer tipo que tenha um método GetAwaiter
acessível que retorne uma instância de um tipo de awaiter . Além disso, o tipo retornado do método GetAwaiter
deve ter o atributo System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Você pode saber mais no artigo sobre os atributos lidos pelo compilador ou na especificação do C# para o padrão de construção de tipo de tarefa .
Este recurso é o complemento para expressões aguardadas, que descreve os requisitos para o operando de await
. Os tipos de retorno assíncronos generalizados permitem que o compilador gere métodos async
que retornam tipos diferentes. Os tipos de retorno assíncrono generalizado habilitaram melhorias de desempenho nas bibliotecas .NET. Como Task e Task<TResult> são tipos de referência, a alocação de memória em caminhos críticos de desempenho, particularmente quando as alocações ocorrem em loops apertados, pode afetar negativamente o desempenho. O suporte para tipos de retorno generalizados significa que você pode retornar um tipo de valor leve em vez de um tipo de referência para evitar mais alocações de memória.
O .NET fornece a estrutura System.Threading.Tasks.ValueTask<TResult> como uma implementação leve de um valor generalizado de retorno de tarefas. O exemplo a seguir usa a estrutura ValueTask<TResult> para recuperar o valor de dois rolos de dados.
class Program
{
static readonly Random s_rnd = new Random();
static async Task Main() =>
Console.WriteLine($"You rolled {await GetDiceRollAsync()}");
static async ValueTask<int> GetDiceRollAsync()
{
Console.WriteLine("Shaking dice...");
int roll1 = await RollAsync();
int roll2 = await RollAsync();
return roll1 + roll2;
}
static async ValueTask<int> RollAsync()
{
await Task.Delay(500);
int diceRoll = s_rnd.Next(1, 7);
return diceRoll;
}
}
// Example output:
// Shaking dice...
// You rolled 8
Escrever um tipo de retorno assíncrono generalizado é um cenário avançado e é direcionado para uso em ambientes especializados. Em vez disso, considere usar os tipos Task
, Task<T>
e ValueTask<T>
, que abrangem a maioria dos cenários para código assíncrono.
Você pode aplicar o atributo AsyncMethodBuilder
a um método assíncrono (em vez da declaração de tipo de retorno assíncrono) para sobrescrever o construtor desse tipo. Normalmente, você aplicaria esse atributo para usar um construtor diferente fornecido no tempo de execução do .NET.
Fluxos assíncronos com IAsyncEnumerable<T>
Um método assíncrono pode retornar um fluxo assíncrono , representado por IAsyncEnumerable<T>. Um fluxo assíncrono fornece uma maneira de enumerar itens lidos de um fluxo quando os elementos são gerados em partes com chamadas assíncronas repetidas. O exemplo a seguir mostra um método assíncrono que gera um fluxo assíncrono:
static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()
{
string data =
@"This is a line of text.
Here is the second line of text.
And there is one more for good measure.
Wait, that was the penultimate line.";
using var readStream = new StringReader(data);
string? line = await readStream.ReadLineAsync();
while (line != null)
{
foreach (string word in line.Split(' ', StringSplitOptions.RemoveEmptyEntries))
{
yield return word;
}
line = await readStream.ReadLineAsync();
}
}
O exemplo anterior lê linhas de uma cadeia de caracteres de forma assíncrona. Depois que cada linha é lida, o código enumera cada palavra na cadeia de caracteres. Os chamadores enumerariam cada palavra usando a instrução await foreach
. O método aguarda quando precisa ler assincronamente a próxima linha da cadeia de caracteres de origem.