Переход на асинхронный: репозиторий



У меня есть большая кодовая база, использующая мои репозитории, которые все реализуют IRespository, и я реализую асинхронные версии методов:



T Find(id);
Task<T> FindAsync(id);
...etc...


Существует несколько видов репозиториев. Самый простой основан на неизменяемой коллекции, где Вселенная сущностей достаточно мала, чтобы заслужить загрузку их всех сразу из БД. Эта нагрузка происходит при первом вызове любого из методов IRepository. Find (4), например, вызовет загрузку, если этого не произошло уже.

Я реализовал это с помощью Lazy . Очень удобно и работает уже много лет.



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



Я не знаю, как объявить себя ленивым-если я делаю это так, как делал всегда,



Lazy<MyCollection<T>> 


Тогда загрузка не будет асинхронной при первом вызове FindAsync (). С другой стороны, если я уйду ...



Lazy<Task<MyCollection<T>>>


Это было бы здорово для FindAsync (), но как синхронный метод вызовет начальную нагрузку без предупреждения Мистера Клири о взаимоблокировке от вызова задачи.Результат?



Спасибо, что уделили мне время!

688   3  

3 ответов:

Проблема с Lazy<T> заключается в том, что существует только один фабричный метод. На самом деле вам нужен синхронный заводской метод, если первый вызов синхронный, и асинхронный заводской метод, если первый вызов асинхронный. Lazy<T> не сделает этого для вас, и AFAIK нет ничего другого встроенного, что предлагает эту семантику.

Вы можете, однако, построить его самостоятельно:

public sealed class SyncAsyncLazy<T>
{
  private readonly object _mutex = new object();
  private readonly Func<T> _syncFunc;
  private readonly Func<Task<T>> _asyncFunc;
  private Task<T> _task;

  public SyncAsyncLazy(Func<T> syncFunc, Func<Task<T>> asyncFunc)
  {
    _syncFunc = syncFunc;
    _asyncFunc = asyncFunc;
  }

  public T Get()
  {
    return GetAsync(true).GetAwaiter().GetResult();
  }

  public Task<T> GetAsync()
  {
    return GetAsync(false);
  }

  private Task<T> GetAsync(bool sync)
  {
    lock (_mutex)
    {
      if (_task == null)
        _task = DoGetAsync(sync);
      return _task;
    }
  }

  private async Task<T> DoGetAsync(bool sync)
  {
    return sync ? _syncFunc() : await _asyncFunc().ConfigureAwait(false);
  }
}

Или вы можете просто использовать этот шаблон, не инкапсулируя его:

private readonly object _mutex = new object();
private Task<MyCollection<T>> _collectionTask;

private Task<MyCollection<T>> LoadCollectionAsync(bool sync)
{
  lock (_mutex)
  {
    if (_collectionTask == null)
      _collectionTask = DoLoadCollectionAsync(sync);
    return _collectionTask;
  }
}

private async Task<MyCollection<T>> DoLoadCollectionAsync(bool sync)
{
  if (sync)
    return LoadCollectionSynchronously();
  else
    return await LoadCollectionAsynchronously();
}

" bool sync" паттерн-это тот, который недавно показал мне Стивен Туб. AFAIK там нет блогов или что-нибудь об этом еще.

Tasks будет работать только один раз, но вы можете await на них столько раз, сколько вы хотите, и вы также можете вызвать Wait() или Result на них после завершения, и это не будет блокировать.

Асинхронные методы преобразуются в машину состояний, которая планирует код после каждого await запуска после завершения ожидаемого. Однако существует оптимизация, при которой, если ожидание уже завершено, код запускается немедленно. Таким образом, ожидание на завершенных ожидающих несет мало накладных расходов.

Для тех, кто небольшие хранилища в памяти, вы можете вернуть завершенный Task с помощью Task.FromResult. И вы можете кэшировать любой Task и ждать его в любое время.

Как синхронный метод инициирует начальную загрузку без выполнения предупреждений Мистера Клири о взаимоблокировке от вызова задачи.Результат?

Вы можете использовать синхронную версию и использовать Task.FromResult для загрузки вашего Lazy<Task<MyCollection<T>>>. Если эта лениво асинхронная операция выставлена наружу, она может запутать, так как она заблокирует. Если это внутренняя ситуация одиночного вызова, я бы пошел с:

private Lazy<Task<MyCollection<T>>> myCollection = new Lazy<Task<MyCollection<T>>>(() => 
{
     var collection = myRepo.GetCollection<T>();
     return Task.FromResult(collection);
}

Comments

    Ничего не найдено.