访问集合的多个线程

时间:2017-03-30 11:44:29

标签: c# multithreading thread-safety

我有以下情况。

我有一个队列,我收到来自的消息。当消息进入时,会创建一个新线程来处理它。

此线程接收消息并将其添加到集合中。然后它检查集合是否包含100个项目,如果是,它将它们发送到其他地方并清除集合。

我无法使用常规列表,因为我修改了集合,枚举无法继续出错。所以我需要使用线程安全集合。

我担心的是,一个线程写入它和它的第100个项目,而它将它们发送到其他地方另一个线程添加到集合中。使它成为101项,触发100的线程然后清除它,我丢失了一个项目。

我不能使用并发包,因为它没有明确的,我不能迭代包并逐个删除,因为消息可能会进入并且被添加得比它可以删除的更快永远不会结束。

ConcurrentStack有一个明确的,但在这种情况下会起作用吗?

一些代码用于演示我的意思,HandleMeasurementMessage发生在每个消息的新线程上。

private static readonly ConcurrentStack<EventHubDatum> EventHubDataBatch = new ConcurrentStack<EventHubDatum>();

private static void HandleMeasurementMessage(IMessage<MessageEnvelope> msg)
{
    /* Do a bunch of stuff to msg */

   EventHubDataBatch.Push(eventHubDatum);

   if(EventHubDataBatch.Count == 100)
   {
      /* Send them off*/
      EventHubDatabatch.Clear();
   }
}

很明显,只有在我没有通过VS2015中的调试器运行枚举时才会出现修改枚举的问题。程序运行一个小时左右就好了。如果我关闭调试器,我会收到这些枚举错误,这就是我尝试切换到线程安全集合的原因。我不确定哪一个是合适的。

调用HandleMeasurementMessage的代码

_busSingle.Consume<MessageEnvelope>(_queueMeasurement, (msg, MessageReceivedInfo) => Task.Factory.StartNew(() =>
            {
                try
                {
                    HandleMeasurementMessage(msg);
                }
                catch (Exception ex)
                {
                    /* Logging stuff*/
                }
            }));

2 个答案:

答案 0 :(得分:3)

我会像这样使用简单的锁:

private static readonly List<EventHubDatum> EventHubDataBatch = new List<EventHubDatum>();        
private static void HandleMeasurementMessage(IMessage<MessageEnvelope> msg)
{
    /* Do a bunch of stuff to msg */

    EventHubDatum[] toSend = null;
    lock (EventHubDataBatch) {
        EventHubDataBatch.Add(eventHubDatum);

        if (EventHubDataBatch.Count == 100) {
            // copy to local
            toSend = EventHubDataBatch.ToArray();
            EventHubDataBatch.Clear();
        }
    }

    if (toSend != null) {
        /* Send them off*/
    }
}

此处的锁定非常简短,因此不应以任何明显的方式影响您的情况。请注意,如果有100个项目 - 我们将它们复制到本地数组并清除源列表,以便在&#34;发送它们之前不保持锁定。操作,可能需要很长时间。

答案 1 :(得分:0)

使用AutoResetEvent之类的同步对象,一次只允许一个线程访问该集合。

使用示例:

static AutoResetEvent MeasureMessageEvent = new AutoResetEvent(true);

private static void HandleMeasurementMessage(IMessage<MessageEnvelope> msg)
{
    /* Do a bunch of stuff to msg */

    // Wait for exclusive access
    MeasureMessageEvent.WaitOne();

    EventHubDataBatch.Push(eventHubDatum);

    if(EventHubDataBatch.Count == 100)
    {
       /* Send them off*/
       EventHubDatabatch.Clear();
    }

    // Release exclusive access
    MeasureMessageEvent.Set();
}