TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.76K subscribers
Post #1689 1.42K
День 1362. #ЗаметкиНаПолях #AsyncTips
Асинхронное освобождение

Задача:
имеется тип с асинхронными операциями, который должен обеспечить освобождение своих ресурсов.

Решение
Есть два распространённых варианта действий.

1. Запрос на отмену всех текущих операций.
Такие типы, как файловые потоки и сокеты, отменяют все существующие операции чтения и записи при закрытии. Определив собственный CancellationTokenSource и передавая этот маркер внутренним операциям, можно сделать нечто похожее. В этом случае Dispose отменит операции, не ожидая их завершения:
class MyClass : IDisposable
{
private readonly CancellationTokenSource _сts =
new CancellationTokenSource();

public async Task<int> CalcAsync()
{
await Task.Delay(
TimeSpan.FromSeconds(2),
_сts.Token);

return 42;
}

public void Dispose()
{
_сts.Cancel();
}
}

Выше приведён упрощённый код. В реальном паттерне Dispose не всё так просто. А также стоит предоставить пользователю возможность передать собственный маркер CancellationToken (используя приём, описанный в этом посте).

При вызове Dispose будут отменены все существующие операции в вызывающем коде:
async Task UseMyClassAsync()
{
Task<int> task;
using (var resource = new MyClass())
{
task = resource.CalcAsync(default);
}
// Выдает OperationCanceledException.
var result = await task;
}
Для некоторых типов (например, HttpClient) такая реализация работает вполне нормально. Однако иногда необходимо убедиться, что будут завершены все операции.

2. Асинхронное освобождение впервые появилось в C# 8.0. Появились интерфейс IAsyncDisposable и команда await using. Таким образом, типы, которые собирались выполнить асинхронную работу при освобождении, теперь получили такую возможность:
class MyClass : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.Delay(TimeSpan.FromSeconds(2));
}
}

Использование:
await using (var myClass = new MyClass())
{
…
} // <--
// Здесь вызывается DisposeAsync (с ожиданием)

Также можно использовать ConfigureAwait(false):
var myClass = new MyClass();
await using (myClass.ConfigureAwait(false))
{
…
} // <--
// Здесь вызывается DisposeAsync (с ожиданием)
// с ConfigureAwait(false).

Асинхронное освобождение определенно проще, а первый подход должен использоваться только в том случае, если это действительно необходимо. Также при желании можно использовать оба подхода одновременно. Это наделит ваш тип семантикой «безопасного завершения работы», если в клиентском коде используется await using, и семантикой «жёсткой отмены», если клиентский код использует Dispose.

Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 11.
  • 👍 11
More from @netdeveloperdiary
  1. Oct 13, 2026День 2813. #ЗаметкиНаПолях #Async Ещё Раз Про Async/Await в C#. Часть 1 1. Почему существу…
  2. Oct 12, 2026День 2812. #Карьера #AI Как Разработчикам Оставаться Полезными, Когда Код Пишет ИИ? По мер…
  3. Oct 11, 2026День 2811. #ВопросыНаСобеседовании Марк Прайс предложил свой набор из 60 вопросов (как тех…
  4. Oct 10, 2026День 2810. #ЧтоНовенького #VSCode Более Быстрый и Лёгкий C# Dev Kit Мы, разработчики, люби…
  5. Oct 9, 2026День 2809. #Карьера 5 Навыков, Которые Помогут Быстрее Стать Сеньором. Окончание Начало 3.…
  6. Oct 8, 2026День 2808. #Карьера 5 Навыков, Которые Помогут Быстрее Стать Сеньором. Начало В ИТ есть се…
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 →