【问题标题】:Can a Task have multiple awaiters?一个任务可以有多个等待者吗?
【发布时间】:2012-11-23 13:30:40
【问题描述】:

我正在为 Windows 8 项目使用异步服务,并且该服务有一些异步调用,一次只能调用一次。

 public async Task CallThisOnlyOnce()
 {
      PropagateSomeEvents();

      await SomeOtherMethod();

      PropagateDifferentEvents();
 }

由于您无法将异步调用封装在 lock 语句中,因此我想到了使用 AsyncLock 模式,但我认为我不妨尝试这样的事情:

 private Task _callThisOnlyOnce;
 public Task CallThisOnlyOnce()
 {
      if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted)
         _callThisOnlyOnce = null;

      if(_callThisOnlyOnce == null)
         _callThisOnlyOnce = CallThisOnlyOnceAsync();

      return _callThisOnlyOnce;
 }

 private async Task CallThisOnlyOnceAsync()
 {
      PropagateSomeEvents();

      await SomeOtherMethod();

      PropagateDifferentEvents();
 }

因此,您最终会得到调用 CallThisOnlyOnceAsync 只同时执行一次,并且多个等待者挂接到同一个任务。

这是一种“有效”的方式吗?还是这种方式有一些缺点?

【问题讨论】:

    标签: c# .net-4.0 windows-runtime task-parallel-library .net-4.5


    【解决方案1】:

    一个任务可以有多个等待者。但是,正如 Damien 指出的那样,您提出的代码存在严重的竞争条件。

    如果您希望每次调用您的方法时都执行代码(但不是同时),请使用AsyncLock。如果您希望代码只执行一次,请使用AsyncLazy

    您提出的解决方案尝试组合多个调用,如果代码尚未运行,则再次执行该代码。这更加棘手,解决方案在很大程度上取决于您需要的确切语义。这是一种选择:

    private AsyncLock mutex = new AsyncLock();
    private Task executing;
    
    public async Task CallThisOnlyOnceAsync()
    {
      Task action = null;
      using (await mutex.LockAsync())
      {
        if (executing == null)
          executing = DoCallThisOnlyOnceAsync();
        action = executing;
      }
    
      await action;
    }
    
    private async Task DoCallThisOnlyOnceAsync()
    {
      PropagateSomeEvents();
    
      await SomeOtherMethod();
    
      PropagateDifferentEvents();
    
      using (await mutex.LockAsync())
      {
        executing = null;
      }
    }
    

    也可以使用Interlocked 执行此操作,但该代码会变得丑陋。

    附:我的AsyncEx library 中有AsyncLockAsyncLazy 和其他async-ready 原语。

    【讨论】:

    • 我喜欢这两个答案,但由于您添加了实施建议,我选择了您的。另外,我尝试使用 Nuget 安装您的库,但在我的 Windows Store 项目中失败(无法解析 Microsoft.Bcl.Async)
    • 尝试选中“包括预发布”复选框。我的包是预发布的(但 NuGet 没有正确检测到),Microsoft.Bcl.Async 也是预发布的(NuGet 可以正确检测到)。
    【解决方案2】:

    如果可能涉及多个线程,此代码看起来非常“活泼”。

    一个例子(我相信还有更多)。假设_callThisOnlyOnce当前是null

    Thread 1                                                          Thread 2
    
    public Task CallThisOnlyOnce()
    {
      if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted)
         _callThisOnlyOnce = null;
    
      if(_callThisOnlyOnce == null)
                                                                       public Task CallThisOnlyOnce()
                                                                       {
                                                                         if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted)
                                                                            _callThisOnlyOnce = null;
    
                                                                         if(_callThisOnlyOnce == null)
                                                                            _callThisOnlyOnce = CallThisOnlyOnceAsync();
    
                                                                         return _callThisOnlyOnce;
                                                                       }
         _callThisOnlyOnce = CallThisOnlyOnceAsync();
    
      return _callThisOnlyOnce;
    }
    

    您现在有 2 个呼叫同时运行。

    对于多个等待者,是的,您可以这样做。我确定我在某处看到过 MS 的示例代码,其中显示了优化,例如Task.FromResult(0) 的结果存储在静态成员中,并在函数想要返回零时返回。

    但是,我未能找到此代码示例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多