迁移到Async:存储库

时间:2015-06-19 19:57:15

标签: c# repository async-await .net-4.5

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

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

有几种存储库。最简单的是基于一个不可变集合,其中实体的范围足够小,值得从DB中一次加载它们。这种负载发生在任何人第一次调用任何IRepository方法时。例如,如果尚未发生,则查找(4)将触发加载。

我已经用Lazy&lt; T>。非常方便,并已工作多年。

我无法在Async上进行冷烤,所以我必须在同步版本旁边添加Async。我的问题是,我不知道哪个将首先调用 - 存储库中的同步或异步方法。

我不知道如何宣布我的懒惰 - 如果我这样做,因为我一直都这样做,

Lazy<MyCollection<T>> 

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

Lazy<Task<MyCollection<T>>>

这对于FindAsync()来说会很好但是同步方法如何触发初始加载而不会因为调用Task.Result而导致Cleary先生发出有关死锁的警告?

感谢您的时间!

3 个答案:

答案 0 :(得分:2)

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

&#34; bool sync&#34;模式是Stephen Toub最近向我展示的模式。 AFAIK那里没有关于它的博客或任何内容。

答案 1 :(得分:0)

Task只会运行一次,但你可以await多次使用它们,你也可以在完成后调用Wait()Result这不会阻止。

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

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

答案 2 :(得分:0)

  

同步方法将如何触发初始加载而不会因为调用Task.Result而导致Cleary先生发出有关死锁的警告?

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