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

本文关键字:异步方法 安全 线程 队列 何使使 | 更新日期: 2023-09-27 18:31:54

我有一个服务可以确保同时显示一个弹出窗口。 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,因为队列仍然为空。

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,但我不确定这是否是最简单的解决方案。

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

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

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

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

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

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