Risposte:
Quando si utilizza async
/ await
, non vi è alcuna garanzia che il metodo che si chiama quando lo si esegue await FooAsync()
effettivamente verrà eseguito in modo asincrono. L'implementazione interna può essere restituita gratuitamente utilizzando un percorso completamente sincrono.
Se stai creando un'API in cui è fondamentale che non blocchi e esegui un po 'di codice in modo asincrono e c'è una possibilità che il metodo chiamato venga eseguito in modo sincrono (blocco efficace), l'utilizzo await Task.Yield()
forzerà il tuo metodo ad essere asincrono e restituirà controllo a quel punto. Il resto del codice verrà eseguito in un secondo momento (a quel punto, potrebbe ancora essere eseguito in modo sincrono) sul contesto corrente.
Ciò può essere utile anche se si esegue un metodo asincrono che richiede un'inizializzazione "long running", ovvero:
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);
}
Senza la Task.Yield()
chiamata, il metodo verrà eseguito in modo sincrono fino alla prima chiamata a await
.
Task.Run
per implementarlo, ExecuteFooOnUIThread
verrà eseguito sul pool di thread, non sul thread dell'interfaccia utente. Con await Task.Yield()
, lo forzate ad essere asincrono in modo che il codice successivo sia ancora eseguito sul contesto corrente (solo in un momento successivo). Non è qualcosa che faresti normalmente, ma è bello che ci sia l'opzione se è richiesta per qualche strano motivo.
ExecuteFooOnUIThread()
durasse molto a lungo, bloccherebbe comunque a lungo il thread dell'interfaccia utente e renderebbe l'IU non rispondente, è corretto?
Internamente, await Task.Yield()
semplicemente accoda la continuazione sul contesto di sincronizzazione corrente o su un thread di pool casuale, se lo SynchronizationContext.Current
è null
.
È implementato in modo efficiente come cameriere personalizzato. Un codice meno efficiente che produce l'effetto identico potrebbe essere semplice come questo:
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()
può essere usato come scorciatoia per alcune strane alterazioni del flusso di esecuzione. Per esempio:
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
}
Detto questo, non riesco a pensare ad alcun caso in cui Task.Yield()
non possa essere sostituito con Task.Factory.StartNew
un pianificatore di attività adeguato.
Guarda anche:
var dialogTask = await showAsync();
?
var dialogTask = await showAsync()
non verrà compilato perché l' await showAsync()
espressione non restituisce un Task
(diversamente da come fa senza await
). Detto questo, se lo fai await showAsync()
, l'esecuzione dopo verrà ripresa solo dopo la chiusura della finestra di dialogo, ecco come è diversa. Questo perché window.ShowDialog
è un'API sincrona (nonostante continui a pompare messaggi). In quel codice, volevo continuare mentre la finestra di dialogo è ancora visualizzata.
Un uso di Task.Yield()
è per evitare un overflow dello stack durante la ricorsione asincrona. Task.Yield()
impedisce la continuazione sincrona. Si noti, tuttavia, che ciò può causare un'eccezione OutOfMemory (come notato da Triynko). La ricorsione senza fine non è ancora sicura e probabilmente stai meglio riscrivendo la ricorsione come un ciclo.
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)
sia abbastanza per impedirlo. (App console, .NET Core 3.1, C # 8)
Task.Yield()
può essere utilizzato in simulazioni di metodi asincroni.
await Task.Yield()
impone che il metodo sia asincrono, perché dovremmo preoccuparci di scrivere un codice asincrono "reale"? Immagina un metodo di sincronizzazione pesante. Per renderlo asincrono, basta aggiungereasync
eawait Task.Yield()
all'inizio e magicamente, sarà asincrono? Sarebbe praticamente come racchiudere tutto il codice di sincronizzazioneTask.Run()
e creare un metodo asincrono falso.