我的存储库有一个很大的代码库,它们都实现了IRespository,并且正在实现方法的异步版本:

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


有几种存储库。最简单的方法基于不可变的集合,其中实体的范围足够小,可以从数据库一次加载所有实体。第一次有人调用任何IRepository方法时,就会发生这种加载。例如,Find(4)将触发加载(如果尚未发生)。

我已经用Lazy 实现了它。非常方便,已经工作了多年。

我无法在Async上使用火鸡,因此必须在同步版本旁边添加Async。我的问题是,我不知道将首先调用哪个-存储库中的同步或异步方法。

我不知道如何声明我的懒惰-如果我像往常一样这样做,

Lazy<MyCollection<T>>


然后在第一次调用FindAsync()时加载它不会异步。另一方面,如果我去

Lazy<Task<MyCollection<T>>>


这对于FindAsync()来说将是一个很好的选择,但是同步方法将如何在不调用Clear。先生警告有关调用Task.Result死锁的警告的情况下触发初始加载?

感谢您的时间!

最佳答案

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();
}


“布尔同步”模式是Stephen Toub最近向我展示的一种模式。 AFAIK尚无博客或相关内容。

07-24 09:45
查看更多