Відповіді:
Коли ви використовуєте 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буде працювати в пулі потоків, а не в потоці UI. З 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.StartNeww / належним планувальником завдань.
Дивитися також:
var dialogTask = await showAsync();?
var dialogTask = await showAsync()не буде компілюватися, оскільки await showAsync()вираз не повертає a Task(на відміну від нього без await). Однак, якщо це зробити await showAsync(), виконання після його відновлення буде відновлено лише після закриття діалогового вікна. Це тому window.ShowDialog, що це синхронний API (незважаючи на те, що він все ще перекачує повідомлення). У цьому коді я хотів продовжити, поки діалогове вікно ще відображається.
Одне використання Task.Yield()- запобігти переповненню стека під час асинхронної рекурсії. Task.Yield()перешкоджає синхронному продовженню. Однак зауважте, що це може спричинити виняток OutOfMemory (як зазначив Трайнко). Нескінченна рекурсія все ще не є безпечною, і вам, ймовірно, краще переписати рекурсію у цикл.
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()введення коду синхронізації та створення підробленого методу асинхронізації.