【问题标题】:How to make asynchronous methods that use a queue thread safe如何使使用队列线程的异步方法安全
【发布时间】:2014-11-24 20:58:25
【问题描述】:

我有一项服务可以确保同时显示一个弹出窗口。 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&lt;T&gt; 有你需要的内置功能。
  • 您当前的语义是所有对AddPopupAsync 的调用在所有弹出窗口都显示之前不会完成(不仅仅是那个特定调用显示的那个)。你确定这是你想要的语义吗?
  • @Stephen Cleary 因为 PopupView 和 PopupViewModel 是单例,所以每当调用 Popup 时,模态弹出窗口的状态都会改变。这就是我使用队列的原因。
  • 将这种类似于 UI 服务的东西视为 UI 的一部分难道不是很有意义吗?即,仅从 UI 上下文调用 AddPopupAsync
  • @Stephen Cleary:如果仅从 UI 线程调用 AddPopupAsync,由于 await 关键字,AddPopupAsync 在工作线程上运行。正确的?您是否想说不需要异步函数,因为 AddPopupAsync 中没有计算绑定的工作,如果不再有异步函数,代码将是线程安全的?

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


【解决方案1】:

要简单地添加线程安全,您应该使用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 不仅是线程安全的,它还为您提供异步协调(通常在消费者和生产者之间)。

【讨论】:

  • 但是 ConcurrentQueue 并不表示使用 ConcurrentQueue 的代码是线程安全的,是吗(请参阅我的问题中的 #2)?
  • @user4287276 ConcurrentQueue 确保对其方法之一的每次调用本身都是线程安全的。这不是锁定机制。如果您需要在代码中进行同步,您应该使用lock(或AsyncLock,如果合适的话)。
  • BufferBlock 没有 peek 方法
  • @ArnoldPistorius 是否有一个TryReceive 方法接受可以用作“窥视”的谓词?
猜你喜欢
  • 2010-10-27
  • 2016-03-12
  • 2021-11-06
  • 2018-08-15
  • 1970-01-01
  • 1970-01-01
  • 2013-03-22
  • 1970-01-01
  • 2018-04-01
相关资源
最近更新 更多