迁移到异步:存储库

本文关键字:存储 异步 迁移 | 更新日期: 2023-09-27 17:57:15

我有一个大型代码库,使用我的存储库,这些存储库都实现了 IRespository,我正在实现方法的异步版本:

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

有几种类型的存储库。最简单的是基于不可变的集合,其中实体的宇宙足够小,值得从数据库一次加载它们。此负载在任何人第一次调用任何 IRepository 方法时发生。例如, Find(4, 如果它还没有发生, 将触发加载。

我已经用懒惰实现了这个。非常方便,已经工作多年了。

我不能在异步上冷火鸡,所以我必须在同步版本旁边添加异步。我的问题是,我不知道哪个会先调用 - 存储库上的同步或异步方法。

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

Lazy<MyCollection<T>> 

那么当首先调用 FindAsync() 时加载它不会是异步的。另一方面,如果我去

Lazy<Task<MyCollection<T>>>

这对于 FindAsync() 来说非常有用,但是同步方法将如何触发初始加载,而不会与 Cleary 先生关于调用 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();
}

"布尔同步"模式是斯蒂芬·图布最近向我展示的模式。AFAIK 还没有关于它的博客或任何东西。

Task只会

运行一次,但您可以根据需要多次await它们,也可以在完成后调用Wait()Result它们,这不会阻塞。

异步方法被转换为状态机,该状态机在每次await完成后安排代码运行。但是,有一个优化,如果等待已经完成,代码会立即运行。因此,等待完成的等待者几乎没有开销。

对于那些较小的内存存储库,您可以使用 Task.FromResult 返回已完成的Task。您可以缓存任何Task并随时等待它。

同步方法将如何触发初始加载,而不会与 Cleary 先生关于调用 Task.Result 的死锁警告相冲突?

您可以使用同步版本并使用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);
}