【问题标题】:Migrating to Async: Repository迁移到异步:存储库
【发布时间】:2015-06-21 09:17:53
【问题描述】:

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

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

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

我已经用 Lazy 实现了这个。非常好用,已经工作多年了。

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

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

Lazy<MyCollection<T>> 

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

Lazy<Task<MyCollection<T>>>

这对于 FindAsync() 来说非常有用,但是同步方法将如何触发初始加载而不会与 Cleary 先生关于调用 Task.Result 导致死锁的警告相冲突?

感谢您的宝贵时间!

【问题讨论】:

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


    【解决方案1】:

    Lazy&lt;T&gt; 的问题在于只有一种工厂方法。如果第一次调用是同步的,那么您真正想要的是同步工厂方法,如果第一次调用是异步的,则需要异步工厂方法。 Lazy&lt;T&gt; 不会为您这样做,而且 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 还没有博客或任何关于它的东西。

    【讨论】:

    • 我希望你能看到这个并参与进来——这真的很有帮助,谢谢!也很喜欢你的书!
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

      您可以使用同步版本并使用Task.FromResult 加载您的Lazy&lt;Task&lt;MyCollection&lt;T&gt;&gt;&gt;。如果这个惰性异步操作暴露在外面,它可能会混淆,因为它会阻塞。如果这是内部单次通话情况,我会选择:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-16
        相关资源
        最近更新 更多