Обложка канала

.NET Разработчик

Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин

.NET Разработчик

4 года назад
Открыть в
День 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.