Я не понимаю что это, как работает и в каких случаях используется. Может кто-нибудь по-русски объяснить?
Ответ Этот метод возвращает специальное значение, предназначенное для передачи оператору await, и в отрыве от этого оператора не имеющее смысла. Конструкция же await Task.Yield() делает довольно простую вещь — прерывает текущий метод и сразу же планирует его продолжение в текущем контексте синхронизации. Используется же эта конструкция для разных целей. Во-первых, эта конструкция может быть использована для немедленного возврата управления вызывающему коду. Например, при вызове из обработчика события событие будет считаться обработанным: protected override async void OnClosing(CancelEventArgs e) { e.Cancel = true await Task.Yield() // (какая-то логика) } Во-вторых, эта конструкция используется для очистки синхронного контекста вызова. Например, так можно "закрыть" текущую транзакцию (ambient transaction): using (var ts = new TransactionScope()) { // ... Foo() // ... ts.Complete() } async void Foo() { // ... тут мы находимся в контексте транзакции if (Transaction.Current != null) await Task.Yield() // ... а тут его уже нет! } В-третьих, эта конструкция может очистить стек вызовов. Это может быть полезным, если программа падает с переполнением стека при обработке кучи вложенных продолжений. Например, рассмотрим упрощенную реализацию AsyncLock: class AsyncLock { private Task unlockedTask = Task.CompletedTask public async Task Lock() { var tcs = new TaskCompletionSource() await Interlocked.Exchange(ref unlockedTask, tcs.Task) return () => tcs.SetResult(null) } } Здесь поступающие запросы на получение блокировки выстраиваются в неявную очередь на продолжениях. Казалось бы, что может пойти не так? private static async Task Foo() { var _lock = new AsyncLock() var unlock = await _lock.Lock() for (var i = 0 i < 100000 i++) Bar(_lock) unlock() } private static async void Bar(AsyncLock _lock) { var unlock = await _lock.Lock() // do something sync unlock() } Здесь продолжение метода Bar вызывается в тот момент, когда другой метод Bar выполняет вызов unlock(). Получается косвенная рекурсия между методом Bar и делегатом unlock, которая быстро сжирает стек и ведет к его переполнению. Добавление же вызова Task.Yield() перенесет исполнение в "чистый" фрейм стека, и ошибка исчезнет: class AsyncLock { private Task unlockedTask = Task.CompletedTask public async Task Lock() { var tcs = new TaskCompletionSource() var prevTask = Interlocked.Exchange(ref unlockedTask, tcs.Task) if (!prevTask.IsCompleted) { await prevTask await Task.Yield() } return () => tcs.SetResult(null) } } Кстати, альтернативный способ починить код выше — использование флага RunContinuationsAsynchronously: class AsyncLock { private Task unlockedTask = Task.CompletedTask public async Task Lock() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously) await Interlocked.Exchange(ref unlockedTask, tcs.Task) return () => tcs.SetResult(null) } } В-четвертых, при использовании в UI-потоке эта конструкция позволяет обработать накопившиеся события ввода-вывода, что полезно при длительных обновлениях интерфейса. Например, при добавлении миллиона строк в таблицу программа не будет реагировать на действия пользователя, пока все строки не будут добавлены. Но если, к примеру, после добавления каждой тысячи строк вставлять вызов await Task.Yield() - программа сможет обрабатывать действия пользователя и не будет выглядеть зависшей. В WinForms для тех же целей можно было использовать метод Application.DoEvents() - но его избыточное использование приводило к переполнению стека. await Task.Yield() - это универсальный способ, который можно использовать как в WinForms, так и в WPF.