Respostas:
Quando você usa async
/ await
, não há garantia de que o método que você chama ao await FooAsync()
executar seja executado de forma assíncrona. A implementação interna é livre para retornar usando um caminho completamente síncrono.
Se você estiver criando uma API em que é fundamental que você não bloqueie e execute algum código de forma assíncrona, e há uma chance de o método chamado ser executado de forma síncrona (efetivamente bloqueada), usar await Task.Yield()
forçará seu método a ser assíncrono e retornará controle nesse ponto. O restante do código será executado posteriormente (nesse ponto, ele ainda poderá ser executado de forma síncrona) no contexto atual.
Isso também pode ser útil se você criar um método assíncrono que requer alguma inicialização "de longa execução", ou seja:
private async void button_Click(object sender, EventArgs e)
{
await Task.Yield(); // Make us async right away
var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later
await UseDataAsync(data);
}
Sem a Task.Yield()
chamada, o método será executado de forma síncrona até a primeira chamada para await
.
Task.Run
para implementá-lo, ExecuteFooOnUIThread
será executado no pool de threads, não no thread da interface do usuário. Com await Task.Yield()
, você o força a ser assíncrono, de forma que o código subsequente ainda seja executado no contexto atual (apenas posteriormente). Não é algo que você normalmente faria, mas é bom que exista a opção se for necessária por algum motivo estranho.
ExecuteFooOnUIThread()
execução fosse muito longa, ele ainda bloqueará o thread da interface do usuário por um longo período de tempo e deixaria a interface sem resposta, está correto?
Internamente, await Task.Yield()
basta enfileirar a continuação no contexto de sincronização atual ou em um encadeamento de pool aleatório, se SynchronizationContext.Current
fornull
.
É implementado eficientemente como garçom personalizado. Um código menos eficiente que produz o efeito idêntico pode ser tão simples quanto isto:
var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
if (sc != null)
sc.Post(_ => tcs.SetResult(true), null);
else
ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(true));
await tcs.Task;
Task.Yield()
pode ser usado como atalho para algumas alterações estranhas no fluxo de execução. Por exemplo:
async Task DoDialogAsync()
{
var dialog = new Form();
Func<Task> showAsync = async () =>
{
await Task.Yield();
dialog.ShowDialog();
}
var dialogTask = showAsync();
await Task.Yield();
// now we're on the dialog's nested message loop started by dialog.ShowDialog
MessageBox.Show("The dialog is visible, click OK to close");
dialog.Close();
await dialogTask;
// we're back to the main message loop
}
Dito isto, não consigo pensar em nenhum caso em Task.Yield()
que não possa ser substituído porTask.Factory.StartNew
um agendador de tarefas adequado.
Veja também:
var dialogTask = await showAsync();
?
var dialogTask = await showAsync()
não será compilado porque a await showAsync()
expressão não retorna a Task
(ao contrário de sem await
). Dito isto, se você fizer await showAsync()
, a execução depois que ela será retomada somente após o fechamento do diálogo, é assim que é diferente. Isso window.ShowDialog
ocorre porque é uma API síncrona (apesar de ainda gerar mensagens). Nesse código, eu queria continuar enquanto a caixa de diálogo ainda é mostrada.
Um dos usos Task.Yield()
é evitar um estouro de pilha ao fazer recursão assíncrona. Task.Yield()
impede a continuação síncrona. Observe, no entanto, que isso pode causar uma exceção OutOfMemory (conforme observado por Triynko). A recursão infinita ainda não é segura e é melhor reescrever a recursão como um loop.
private static void Main()
{
RecursiveMethod().Wait();
}
private static async Task RecursiveMethod()
{
await Task.Delay(1);
//await Task.Yield(); // Uncomment this line to prevent stackoverlfow.
await RecursiveMethod();
}
await Task.Delay(1)
suficiente para impedi-lo. (Aplicativo para console, .NET Core 3.1, C # 8)
Task.Yield()
pode ser usado em implementações simuladas de métodos assíncronos.
await Task.Yield()
força o método a ser assíncrono, por que nos preocuparíamos em escrever código assíncrono "real"? Imagine um método de sincronização pesada. Para torná-lo assíncrono, basta adicionarasync
eawait Task.Yield()
no início e magicamente, será assíncrono? Isso seria como envolver todo o código de sincronizaçãoTask.Run()
e criar um método assíncrono falso.