2012-11-23 36 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)的結果被存儲在一個靜態成員中,並在函數想要返回零時返回。

但是,我找不到這個代碼示例。