J'utilise beaucoup async/wait et Task
mais je n'ai jamais utilisé Task.Yield()
, et pour être honnête, même avec toutes les explications, je ne comprends pas pourquoi j'aurais besoin de cette méthode.
Quelqu'un peut-il donner un bon exemple où Yield()
est requis?
Lorsque vous utilisez async
/await
, rien ne garantit que la méthode que vous appelez lorsque vous exécutez await FooAsync()
s'exécutera de manière asynchrone. L'implémentation interne est libre de retourner en utilisant un chemin complètement synchrone.
Si vous créez une API dans laquelle il est essentiel de ne pas bloquer et d'exécuter du code de manière asynchrone, et qu'il est possible que la méthode appelée s'exécute de manière synchrone (bloquant effectivement), l'utilisation de await Task.Yield()
forcera votre méthode à être asynchrone et renvoyer le contrôle à ce point. Le reste du code sera exécuté ultérieurement (à ce stade, il peut toujours fonctionner de manière synchrone) sur le contexte actuel.
Cela peut aussi être utile si vous créez une méthode asynchrone nécessitant une initialisation "longue", c'est-à-dire:
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);
}
Sans l'appel Task.Yield()
, la méthode s'exécutera de manière synchrone jusqu'au premier appel de await
.
En interne, await Task.Yield()
met simplement la continuation en file d'attente sur le contexte de synchronisation actuel ou sur un thread de pool aléatoire, si SynchronizationContext.Current
est null
.
C'est implémenté efficacement comme attente personnalisée. Un code moins efficace produisant un effet identique pourrait être aussi simple que cela:
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()
peut être utilisé comme raccourci pour certaines modifications étranges du flux d'exécution. Par exemple:
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
}
Cela dit, je ne vois aucun cas où Task.Yield()
ne puisse pas être remplacé par Task.Factory.StartNew
avec le bon planificateur de tâches.
Voir également:
Une utilisation de Task.Yield()
consiste à éviter un débordement de pile lors de la récursivité asynchrone. Task.Yield()
empêche la poursuite synchrone.
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();
}