Что такое Task.Yield()?

301
12 апреля 2017, 11:32

Я не понимаю что это, как работает и в каких случаях используется. Может кто-нибудь по-русски объяснить?

Answer 1

Этот метод возвращает специальное значение, предназначенное для передечи оператору await и в отрыве от этого оператора не имеющее смысла.

Конструкция же 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<Action> Lock()
    {
        var tcs = new TaskCompletionSource<object>();
        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<Action> Lock()
    {
        var tcs = new TaskCompletionSource<object>();
        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<Action> Lock()
    {
        var tcs = new TaskCompletionSource<object>(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.

READ ALSO
Создание полупрозрачной Panel

Создание полупрозрачной Panel

Нужно, чтобы при нажатии на button появлялась panel поверх всех компонентовPanel должна быть чёрного цвета и полупрозрачная

371
C# парсинг IP с разными октетами

C# парсинг IP с разными октетами

Здравствуйте, задача написать регулярное выражение для выборки данных следующим образом: в результате должны остаться правильные IP-адреса...

257
Как сделать сортировку в столбцах у `ListView`?

Как сделать сортировку в столбцах у `ListView`?

Собственно есть listview, как сделать сортировку по данным в столбцах? Есть столбец процессов - нужно по нажатию на колонку, сделать сортировку...

367
Ошибка при запуске проекта ASP. NET Core 1.1

Ошибка при запуске проекта ASP. NET Core 1.1

При запуске проекта появляется окно со следующей ошибкой "ошибка при попытке определить идентификатор процесса dotnetexe", запускаю на IIS в 17 студии

183