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

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

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

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

4 года назад
Открыть в
День 1328. #ЗаметкиНаПолях #AsyncTips Асинхронное Создание Объектов: Фабрики Задача: вы создаёте тип, который требует выполнения некоторой асинхронной работы в конструкторе. Решение Конструкторы не могут объявляться с async; кроме того, они не могут содержать ключевое слово await. Конечно, использование await в конструкторе могло бы быть полезным, но это привело бы к существенному изменению языка C#. Одна из возможностей — использовать конструктор в паре с инициализирующим async-методом, чтобы тип использовался следующим образом:
var instance = new MyAsyncClass();
await instance.InitAsync();

У такого подхода есть недостатки. Разработчик может забыть вызвать метод InitAsync, а экземпляр не может использоваться сразу же после выполнения конструктора. Вот более качественное решение, которое основано на применении паттерна асинхронного фабричного метода:
class AsyncClass
{
  private AsyncClass()
  {
  }
  
  private async Task<AsyncClass> InitAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
    return this;
  }
  
  public static Task<AsyncClass> CreateAsync()
  {
    var result = new AsyncClass();
    return result.InitAsync();
  }
}

Конструктор и метод InitAsync объявлены приватными, чтобы они не могли использоваться в вызывающем коде; экземпляры могут создаваться только одним способом — статическим фабричным методом CreateAsync. Вызывающий код не может обратиться к экземпляру до того, как инициализация будет завершена. Экземпляр может создаваться следующим образом:
var instance = await AsyncClass.CreateAsync();

Главное преимущество этого паттерна заключается в том, что вызывающий код никак не сможет получить неинициализированный экземпляр AsyncClass. К сожалению, в некоторых сценариях этот способ не работает — в частности, когда в коде используется провайдер внедрения зависимостей. Ни одна заметная библиотека внедрения зависимостей или инверсии управления не работает с async-кодом. Если вы окажетесь в одной из таких ситуаций, существует пара альтернатив, которые также стоит рассмотреть. Если создаваемый экземпляр в действительности является общим ресурсом, можно использовать ленивую инициализацию. В противном случае можно воспользоваться паттерном асинхронной инициализации, который рассмотрим позже. Пример того, как поступать не следует:
class AsyncClass
{
  public AsyncClass()
  {
    InitAsync();
  }
  // ПЛОХОЙ КОД!!
  private async void InitAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
  }
}

На первый взгляд решение может показаться разумным: вы получаете обычный конструктор, который запускает асинхронную операцию; при этом у него есть ряд недостатков, обусловленных использованием async void. Первая проблема заключается в том, что при завершении конструктора экземпляр всё ещё продолжает асинхронно инициализироваться, и не существует очевидного способа определить, когда завершится асинхронная инициализация. Вторая проблема связана с обработкой ошибок: любые исключения, выданные из InitAsync, не могут быть перехвачены секциями catch, окружающими вызов конструктора объекта. Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 11.