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

use*_*276 5 .net c# 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);
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 如果队列为空,Peek将抛出异常
  2. 如果线程A在Popup中的第一个语句之前被抢占,则另一个线程B将不会等待挂起的弹出A,因为队列仍然是空的.

Popup方法与a一起使用TaskCompletionSource,在调用其SetResult方法之前,调用它popupQueue.Dequeue().

我考虑使用原子弹TryPeekConcurrentQueue为了使#1线程安全的:

do
{
    Task<int> result;

    bool success = popupQueue.TryPeek(out result);

    if (!success) break;
    await result;
} 
while (true);
Run Code Online (Sandbox Code Playgroud)

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

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

i3a*_*non 7

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

但是,如果你想要一个队列,你可以await异步(这意味着你没有忙等待或在等待时阻塞一个线程)你应该使用TPL Dataflow BufferBlock,它与AsyncProducerConsumerCollectionMS已经为你实现的非常类似:

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

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