2014-11-24 103 views
5

我有一個服務可以確保只有一個彈出窗口同時顯示。 AddPopupAsync可以同時調用,即彈出是開放的,而另外10個AddPopupAsync請求順路代碼:如何使使用隊列線程安全的異步方法

public async Task<int> AddPopupAsync(Message message) 
{ 
    //[...] 

    while (popupQueue.Count != 0) 
    { 
     await popupQueue.Peek(); 
    } 

    return await Popup(interaction); 
} 

但我可以現貨兩個不必要的東西,可以因爲缺乏線程安全的發生:

  1. 如果隊列爲空,皮克將拋出一個異常
  2. 如果一個線程A在彈出的第一條語句之前搶佔,另一個線程B不會等待掛起的彈出菜單的自隊列仍然是空的。

Popup方法的工作原理與TaskCompletionSource,並調用其SetResult方法之前,popupQueue.Dequeue()被調用。

我想利用ConcurrentQueue原子TryPeek爲了使#1線程安全:

do 
{ 
    Task<int> result; 

    bool success = popupQueue.TryPeek(out result); 

    if (!success) break; 
    await result; 
} 
while (true); 

然後我讀到一個AsyncProducerConsumerCollection,但我不知道這是最簡單的解決方案。

我怎樣才能以簡單的方式確保線程安全?謝謝。

+1

我想你已經回答了你自己的問題。 'ConcurrentQueue '有你需要的內置。 – 2014-11-24 12:42:44

+0

您目前的語義是,所有對「AddPopupAsync」的調用都不會完成,直到顯示所有彈出窗口(不只是該特定調用顯示的那個)。你確定這是你想要的語義嗎? – 2014-11-24 12:51:20

+0

@Stephen Cleary由於PopupView和PopupViewModel是單例,每當調用Popup時,模態彈出窗口的狀態都會更改。這就是我使用隊列的原因。 – user4287276 2014-11-24 13:12:07

回答

8

要簡單地添加線程安全性,您應該使用ConcurrentQueue。這是一個線程安全的隊列實現,您可以像使用隊列一樣使用它,而不用擔心併發。

但是,如果你想有一個隊列,你可以await異步(這意味着你不是忙等待或阻塞的線程在等待),你應該使用TPL數據流的BufferBlock這是非常相似的AsyncProducerConsumerCollection只已經爲你實現MS:

var buffer = new BufferBlock<int>(); 
await buffer.SendAsync(3); // Instead of enqueue. 
int item = await buffer.ReceiveAsync(); // instead of dequeue. 

ConcurrentQueue是高水平爭罰款線程safeness,但浪費了。 BufferBlock不僅是線程安全的,它還爲您提供異步協調(通常位於消費者和生產者之間)。

+0

但ConcurrentQueue並不表示使用ConcurrentQueue的代碼是線程安全的,請執行此操作(請參閱我的問題中的#2)? – user4287276 2014-11-24 13:11:38

+0

@ user4287276 ConcurrentQueue確保每個對其方法之一的調用都是線程安全的。這不是一個鎖定機制。如果你的代碼需要同步,你應該使用'lock'(或者'AsyncLock',如果合適的話)。 – i3arnon 2014-11-24 13:27:32