Ответы:
Когда вы используете async
/ await
, нет никакой гарантии, что метод, который вы вызываете, когда вы делаете, await FooAsync()
будет работать асинхронно. Внутренняя реализация может свободно возвращаться, используя полностью синхронный путь.
Если вы создаете API, в котором важно, чтобы вы не блокировали и выполняли некоторый код асинхронно, и есть вероятность, что вызываемый метод будет работать синхронно (эффективно блокируя), использование await Task.Yield()
заставит ваш метод быть асинхронным и вернуть контроль в этой точке. Остальная часть кода будет выполняться позже (в этот момент он все еще может выполняться синхронно) в текущем контексте.
Это также может быть полезно, если вы делаете асинхронный метод, который требует некоторой «длительной» инициализации, то есть:
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);
}
Без Task.Yield()
вызова метод будет выполняться синхронно вплоть до первого вызова await
.
Task.Run
для его реализации, ExecuteFooOnUIThread
будет работать в пуле потоков, а не поток пользовательского интерфейса. С помощью await Task.Yield()
этого вы заставляете его быть асинхронным таким образом, чтобы последующий код все еще выполнялся в текущем контексте (только в более поздний момент времени). Это не то, что вы обычно делаете, но приятно, что есть опция, если она требуется по какой-то странной причине.
ExecuteFooOnUIThread()
работал очень долго, он в какой-то момент блокировал бы поток пользовательского интерфейса на долгое время и сделал бы пользовательский интерфейс невосприимчивым, это правильно?
Внутренне await Task.Yield()
просто помещает в очередь продолжение либо в текущем контексте синхронизации, либо в потоке случайного пула, если он SynchronizationContext.Current
есть null
.
Он эффективно реализован как пользовательский ожидающий. Менее эффективный код, производящий идентичный эффект, может быть таким простым:
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()
может использоваться как ярлык для некоторых странных изменений потока выполнения. Например:
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
}
Тем не менее, я не могу вспомнить ни одного случая, когда Task.Yield()
не может быть заменен с Task.Factory.StartNew
/ надлежащим планировщиком задач.
Смотрите также:
var dialogTask = await showAsync();
?
var dialogTask = await showAsync()
не будет компилироваться, потому что await showAsync()
выражение не возвращает a Task
(в отличие от этого без await
). Тем не менее, если вы это сделаете await showAsync()
, выполнение после того, как оно будет возобновлено только после закрытия диалога, вот как это отличается. Это потому, что window.ShowDialog
синхронный API (несмотря на то, что он по-прежнему качает сообщения). В этом коде я хотел продолжить, пока диалог все еще отображается.
Одним из применений Task.Yield()
является предотвращение переполнения стека при выполнении асинхронной рекурсии. Task.Yield()
предотвращает синхронное продолжение. Обратите внимание, однако, что это может вызвать исключение OutOfMemory (как отметил Triynko). Бесконечная рекурсия все еще небезопасна, и вам, вероятно, лучше переписать рекурсию как цикл.
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)
достаточно, чтобы предотвратить это. (Консольное приложение, .NET Core 3.1, C # 8)
Task.Yield()
может использоваться в имитационных реализациях асинхронных методов.
await Task.Yield()
метод заставляет метод быть асинхронным, зачем нам писать «настоящий» асинхронный код? Представьте себе тяжелый метод синхронизации. Чтобы сделать это асинхронным, просто добавьтеasync
иawait Task.Yield()
в начале, и волшебным образом, это будет асинхронно? Это было бы похоже на обертывание всего кода синхронизацииTask.Run()
и создание ложного асинхронного метода.