如何使用队列线程安全的异步方法

时间:2014-11-24 12:34:06

标签: c# .net thread-safety async-await

我有一项服务可以确保同时显示一个弹出窗口。 AddPopupAsync可以同时调用,即弹出窗口打开而另外10个AddPopupAsync请求丢失。代码:

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

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

    return await Popup(interaction);
}

但是由于缺乏线程安全性,我可以发现可能发生的两件不必要的事情:

  1. 如果队列为空,Peek将抛出异常
  2. 如果线程A在Popup中的第一个语句之前被抢占,则另一个线程B将不会等待挂起的弹出A,因为队列仍然是空的。
  3. 弹出方法适用于TaskCompletionSource,在调用SetResult方法之前,会调用popupQueue.Dequeue()

    我考虑使用TryPeek中的原子ConcurrentQueue来使#1线程安全:

    do
    {
        Task<int> result;
    
        bool success = popupQueue.TryPeek(out result);
    
        if (!success) break;
        await result;
    } 
    while (true);
    

    然后我读到了AsyncProducerConsumerCollection,但我不确定这是否是最简单的解决方案。

    如何以简单的方式确保线程安全?谢谢。

1 个答案:

答案 0 :(得分:7)

要简单地添加线程安全性,您应该使用ConcurrentQueue。它是队列的线程安全实现,您可以像使用队列一样使用它,而不必担心并发。

但是如果你想要一个队列,你可以await异步(这意味着你没有忙等待或在等待时阻塞一个线程)你应该使用TPL Dataflow的BufferBlock非常类似于AsyncProducerConsumerCollection仅由MS实施:

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

ConcurrentQueue适用于线程安全,但在高级别争用中会浪费。 BufferBlock不仅是线程安全的,它还为您提供异步协调(通常在消费者和生产者之间)。