2012-11-23 35 views
9

我正忙着为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上。

这是做到这一点的“有效”方式还是这种方法存在一些缺陷?

回答

6

任务可以有多个等待者。但是,正如达米恩指出的那样,您提出的代码存在严重的竞争状况。

如果您希望在每次调用方法时(但不是同时)执行代码,请使用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.我有AsyncLock,AsyncLazy和其他async已经在我的AsyncEx library原语。

+0

我喜欢这两个答案,但既然你添加了一个实施建议,我选择了你的。另外,我尝试使用Nuget安装库,但在我的Windows应用商店项目中失败(无法解析Microsoft.Bcl.Async) – UrbanEsc

+1

尝试检查“包括预发布”复选框。我的软件包是预发行的(但NuGet没有正确检测到),'Microsoft.Bcl.Async'也是预发行的(NuGet能够正确检测到)。 –

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)的结果被存储在一个静态成员中,并在函数想要返回零时返回。

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