TGViewer
C# (C Sharp) programming C# (C Sharp) programming @csharp_ci · 18.1K subscribers
Post #785 4.56K
🖥 Факт дня — Task.Yield()

Повторение — мать учения и основа научного метода, так что приступим)
Task.Yield() возвращает специальное значение, предназначенное для передачи оператору 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<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);
}
}


@csharp_ci
More from @csharp_ci
  1. Oct 14, 2026Helm-чарты прямо из C#, без запуска `helm` 🛠 HelmSharp позволяет рендерить шаблоны и упра…
  2. Oct 14, 2026Альфа-Банк ищет скиллового коллегу QA Fullstack на C# Предлагаем полную удалёнку или гибри…
  3. Oct 13, 2026🧱 Модульный монолит легко потерять одним удобным JOIN. Допустим, модуль заказов напрямую…
  4. Oct 11, 2026Задачка по C# 14: обновили проект на .NET 10, и код молча начал работать иначе Что выведет…
  5. Oct 9, 2026Post #1901
  6. Oct 7, 2026⚡️ Microsoft переработала C# Dev Kit: меньше памяти, быстрее запуск В версии 11.0 шесть пр…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →