Відповіді:
Коли ви використовуєте 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.StartNew
w / належним планувальником завдань.
Дивитися також:
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()
введення коду синхронізації та створення підробленого методу асинхронізації.