Antworten:
Wenn Sie async
/ verwenden await
, gibt es keine Garantie dafür, dass die Methode, die Sie dabei aufrufen await FooAsync()
, tatsächlich asynchron ausgeführt wird. Die interne Implementierung kann über einen vollständig synchronen Pfad zurückgegeben werden.
Wenn Sie eine API erstellen, bei der es wichtig ist, dass Sie nicht blockieren und Code asynchron ausführen, und die aufgerufene Methode await Task.Yield()
möglicherweise synchron ausgeführt wird (effektiv blockiert), erzwingt die Verwendung, dass Ihre Methode asynchron ist, und kehrt zurück Kontrolle an diesem Punkt. Der Rest des Codes wird zu einem späteren Zeitpunkt (zu diesem Zeitpunkt wird er möglicherweise noch synchron ausgeführt) im aktuellen Kontext ausgeführt.
Dies kann auch nützlich sein, wenn Sie eine asynchrone Methode erstellen, die eine "lang laufende" Initialisierung erfordert, z.
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);
}
Ohne den Task.Yield()
Aufruf wird die Methode bis zum ersten Aufruf von synchron ausgeführt await
.
Task.Run
, um es zu implementieren, ExecuteFooOnUIThread
wird es im Thread-Pool ausgeführt, nicht im UI-Thread. Mit await Task.Yield()
erzwingen Sie, dass es so asynchron ist, dass der nachfolgende Code immer noch im aktuellen Kontext ausgeführt wird (nur zu einem späteren Zeitpunkt). Es ist nicht etwas, was Sie normalerweise tun würden, aber es ist schön, dass es die Option gibt, wenn es aus irgendeinem seltsamen Grund erforderlich ist.
ExecuteFooOnUIThread()
es sehr lange laufen würde, würde es den UI-Thread irgendwann noch für eine lange Zeit blockieren und die UI nicht mehr reagieren lassen. Ist das richtig?
Intern müssen Sie await Task.Yield()
die Fortsetzung einfach entweder im aktuellen Synchronisationskontext oder in einem zufälligen Pool-Thread in die Warteschlange stellen, falls dies der Fall SynchronizationContext.Current
ist null
.
Es wird effizient als benutzerdefinierter Kellner implementiert. Ein weniger effizienter Code, der den identischen Effekt erzeugt, könnte so einfach sein:
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()
kann als Abkürzung für einige seltsame Änderungen des Ausführungsflusses verwendet werden. Beispielsweise:
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
}
Das heißt, ich kann mir keinen Fall Task.Yield()
vorstellen, in dem nicht durch einen Task.Factory.StartNew
richtigen Taskplaner ersetzt werden kann.
Siehe auch:
var dialogTask = await showAsync();
?
var dialogTask = await showAsync()
wird nicht kompiliert, da der await showAsync()
Ausdruck kein a zurückgibt Task
(im Gegensatz zu ohne await
). Wenn Sie dies jedoch tun await showAsync()
, wird die Ausführung nach dem Schließen des Dialogfelds erst wieder aufgenommen. So ist es anders. Das liegt daran, dass window.ShowDialog
es sich um eine synchrone API handelt (obwohl sie immer noch Nachrichten pumpt). In diesem Code wollte ich fortfahren, während der Dialog noch angezeigt wird.
Eine Verwendung Task.Yield()
besteht darin, einen Stapelüberlauf zu verhindern, wenn eine asynchrone Rekursion durchgeführt wird. Task.Yield()
verhindert synchrone Fortsetzung. Beachten Sie jedoch, dass dies eine OutOfMemory-Ausnahme verursachen kann (wie von Triynko angegeben). Endlose Rekursion ist immer noch nicht sicher und Sie sollten die Rekursion wahrscheinlich besser als Schleife umschreiben.
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)
ausreicht, um es zu verhindern. (Konsolen-App, .NET Core 3.1, C # 8)
Task.Yield()
kann in Scheinimplementierungen von asynchronen Methoden verwendet werden.
await Task.Yield()
die Methode asynchron sein soll, warum sollten wir uns dann die Mühe machen, "echten" asynchronen Code zu schreiben? Stellen Sie sich eine schwere Synchronisationsmethode vor. Um es asynchron zu machen, fügen Sie einfach hinzuasync
undawait Task.Yield()
am Anfang und auf magische Weise wird es asynchron sein? Das wäre so ziemlich so, als würde man den gesamten Synchronisierungscode einpackenTask.Run()
und eine gefälschte asynchrone Methode erstellen.