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

我正在为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调用,并且多个awaiters挂钩在同一个Task上。

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

任务可以有多个等待者。 然而,正如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也可以做到这一点,但代码变得丑陋。

PS我的AsyncEx库中有AsyncLockAsyncLazy和其他async -ready原语。

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

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

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