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

6

我有一个服务,可以确保同一时间只显示一个弹出窗口。可以并发调用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 将不会等待挂起的 Popup 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,但我不确定这是否是最简单的解决方案。

有什么简单的方法可以确保线程安全吗?谢谢。


1
我认为你已经回答了自己的问题。ConcurrentQueue<T>内置了你所需要的功能。 - Yuval Itzchakov
@Stephen Cleary 因为 PopupView 和 PopupViewModel 是单例,每当调用 Popup 时,模态弹出窗口的状态就会改变。这就是我使用队列的原因。 - user4287276
1
把这个 UI-服务-类似-的东西看作是 UI 的一部分,只从 UI 上下文中调用 AddPopupAsync,这样做不是很合理吗? - Stephen Cleary
@Stephen Cleary:如果AddPopupAsync仅从UI线程调用,则由于await关键字,AddPopupAsync将在工作线程上运行。对吧?您是否想说,由于AddPopupAsync中没有计算绑定的工作,因此不需要异步函数,如果不再有异步函数,则代码将是线程安全的? - user4287276
await 不会引入工作线程。除非你从 其他线程 调用 AddPopupAsync,否则它不需要是线程安全的。了解有关 async 如何工作的更多信息,请查看我的 async 简介 - Stephen Cleary
显示剩余2条评论
1个回答

8
为了简单地实现线程安全,您应该使用ConcurrentQueue。它是一个线程安全的队列实现,您可以像使用队列一样使用它,而不必担心并发问题。
但是,如果您想异步await(这意味着您在等待时不会忙等或阻塞线程)使用TPL Dataflow的BufferBlock,它非常类似于AsyncProducerConsumerCollection,只不过已经由微软为您实现。
var buffer = new BufferBlock<int>();
await buffer.SendAsync(3); // Instead of enqueue.
int item = await buffer.ReceiveAsync(); // instead of dequeue.

ConcurrentQueue虽然可以保证线程安全,但在高并发情况下会浪费资源。而BufferBlock不仅可以保证线程安全,还可以提供异步协调(通常在消费者和生产者之间)。


但是ConcurrentQueue并没有表明使用ConcurrentQueue的代码是线程安全的,对吗(请参见我问题中的#2)? - user4287276
@user4287276 ConcurrentQueue 通过自身确保每个方法的调用都是线程安全的,它不是一种锁定机制。如果您的代码需要同步,应该使用 lock(或者如果适当的话,使用 AsyncLock)。 - i3arnon
BufferBlock 没有 peek 方法。 - Arnold Pistorius
1
@ArnoldPistorius 它是否有一个接受谓词的 TryReceive 方法,可以用作“peek”? - i3arnon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接