一个任务可以有多个等待者吗?

时间:2012-11-23 13:30:41

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

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

 public async Task CallThisOnlyOnce()
 {
      PropagateSomeEvents();

      await SomeOtherMethod();

      PropagateDifferentEvents();
 }

由于你无法在锁定语句中封装异步调用,我想到使用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调用,并且多个等待者都挂在同一个任务上。

这是一种“有效”的方法吗?或者这种方法有一些缺点吗?

2 个答案:

答案 0 :(得分:7)

任务可以有多个等待者。但是,正如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执行此操作,但该代码很难看。

P.S。我的AsyncEx library中有AsyncLockAsyncLazy和其他async - 就绪原语。

答案 1 :(得分:4)

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

一个例子(我确信还有更多)。假设_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)的结果存储在一个静态成员中,并在函数想要返回零时返回。

但是,我找不到这个代码示例是不成功的。