Réponses:
Lorsque vous utilisez async
/ await
, il n'y a aucune garantie que la méthode que vous appelez lorsque vous le await FooAsync()
ferez s'exécutera réellement de manière asynchrone. L'implémentation interne est libre de revenir en utilisant un chemin complètement synchrone.
Si vous créez une API où il est essentiel de ne pas bloquer et d'exécuter du code de manière asynchrone, et qu'il y a une chance que la méthode appelée s'exécute de manière synchrone (blocage efficace), l'utilisation await Task.Yield()
force votre méthode à être asynchrone et retourne contrôle à ce point. Le reste du code s'exécutera ultérieurement (à ce moment, il peut toujours s'exécuter de manière synchrone) sur le contexte actuel.
Cela peut également être utile si vous créez une méthode asynchrone qui nécessite 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' Task.Yield()
appel, la méthode s'exécutera de manière synchrone jusqu'au premier appel à await
.
Task.Run
pour l'implémenter, ExecuteFooOnUIThread
s'exécutera sur le pool de threads, pas sur le thread d'interface utilisateur. Avec await Task.Yield()
, vous le forcez à être asynchrone de manière à ce que le code suivant soit toujours exécuté sur le contexte actuel (juste à un moment ultérieur). Ce n'est pas quelque chose que vous feriez normalement, mais c'est bien qu'il y ait l'option si c'est nécessaire pour une raison étrange.
ExecuteFooOnUIThread()
était très longue, elle bloquerait encore le thread d'interface utilisateur pendant un certain temps et rendrait l'interface utilisateur non réactive, est-ce correct?
En interne, met await Task.Yield()
simplement la suite en file d'attente sur le contexte de synchronisation actuel ou sur un thread de pool aléatoire, si tel SynchronizationContext.Current
est le cas null
.
Il est efficacement mis en œuvre comme serveur personnalisé. Un code moins efficace produisant l'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 de flux d'exécution étranges. 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 peux penser à aucun cas où Task.Yield()
ne peut pas être remplacé par un Task.Factory.StartNew
bon planificateur de tâches.
Voir également:
var dialogTask = await showAsync();
?
var dialogTask = await showAsync()
ne compilera pas car l' await showAsync()
expression ne renvoie pas de Task
(contrairement à ce qui se passe sans await
). Cela dit, si vous le faites await showAsync()
, l'exécution ne reprendra qu'après la fermeture de la boîte de dialogue, c'est différent. C'est parce que window.ShowDialog
c'est une API synchrone (malgré qu'elle pompe toujours des messages). Dans ce code, je voulais continuer pendant que la boîte de dialogue est toujours affichée.
Une utilisation de Task.Yield()
est d'empêcher un débordement de pile lors de la récursion asynchrone. Task.Yield()
empêche la poursuite syncrone. Notez cependant que cela peut provoquer une exception OutOfMemory (comme l'a noté Triynko). La récursion sans fin n'est toujours pas sûre et vous feriez probablement mieux de réécrire la récursivité en boucle.
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)
soit suffisant pour l'empêcher. (Application console, .NET Core 3.1, C # 8)
Task.Yield()
peut être utilisé dans des implémentations simulées de méthodes asynchrones.
await Task.Yield()
force la méthode à être asynchrone, pourquoi prendrions-nous la peine d'écrire du "vrai" code asynchrone? Imaginez une méthode de synchronisation lourde. Pour le rendre asynchrone, il suffit d'ajouterasync
etawait Task.Yield()
au début et comme par magie, ce sera asynchrone? Ce serait un peu comme encapsuler tout le code de synchronisationTask.Run()
et créer une fausse méthode asynchrone.