BlockingCollection Consumer正在重复输出

时间:2015-08-27 20:54:32

标签: c# task-parallel-library producer-consumer blockingcollection

TL; DR我有一个应用程序正在后台从USB设备读取消息,并在屏幕上显示消息。我正在使用BlockingCollection,因为我需要快速读取消息,因此设备无法获得BufferOverflow。

我正在阅读这样的消息(我的制片人):

private void ReadMessages(BlockingCollection<object> logMessages)
{
   uint numMsgs;
   Status status;
   Message[] msgs = new Message[10];
   while(!logMessages.IsAddingCompleted)
   {
      numMsgs = (uint) msgs.Length;
      status = readMessages(channel, msgs, ref numMsgs, 1000);
      if(status == Status.ERR_BUFFER_OVERFLOW)
      {
         logMessages.Add("BUFFER OVERFLOW - MESSAGES LOST!");
         logMessages.Add(CopyMessages(msgs, numMsgs));
      }
      else if(status == Status.STATUS_NOERROR)
      {
         logMessages.Add(CopyMessages(msgs, numMsgs));
      }
      else
      {
         throw new Exception("Error");
      }
}

readMessages()方法将填充msgs数组,并读取Message个对象,numMsgs引用包含已读取的消息数(最多10个)。我使用了一个名为CopyMessages()的函数,所以我只传递一个大小合适的Message[]。即如果读取了5条消息,我发送Message[5]而不是Message[10]

我读了这样的消息(我的消费者):

private void DisplayMessages(BlockingCollection<object> messages)
{
    string[] msgs;
    try
    {
        foreach (var item in messages.GetConsumingEnumerable(_cancellationTokenSource.Token))
        {
            if (item is string)
            {
                msgs = new string[] { item.ToString() };
            }
            else if (item is PassThruMsg[])
            {
                msgs = FormatMessages((PassThruMsg[])item);
            }
            else
            {
                msgs = new string[0];
            }

            Task.Factory.StartNew(new Action(() => outputTextBox.AppendText(String.Join(Environment.NewLine, msgs) + Environment.NewLine)), _cancellationTokenSource.Token, TaskCreationOptions.None, uiContext);
        }
    }
    catch (OperationCanceledException)
    {
        //TODO:
    }
}

我在点击按钮内启动任务,如下所示:

var results = new BlockingCollection<object>();

var display = Task.Factory.StartNew(() => DisplayMessages(results));
var readMessages = Task.Factory.StartNew(() => ReadMessages(results));

Task[] tasks = new Task[] { display, readMessages };

try
{
    await Task.Factory.ContinueWhenAll(tasks, result => { results.CompleteAdding(); }, _cancellationTokenSource.Token, TaskContinuationOptions.None, uiContext);
}
catch (TaskCanceledException)
{
    //TODO:
}

这样可以正常工作,当无条件运行时,它会毫无问题地从设备打印消息。但是,在设备开始在非常繁重的负载下工作(消费者被如此快速地调用它暂时锁定UI)之后我注意到输出文本框正在重复值。据我所知,GetConsumingEnumerable()也会从阻止集合中删除项目,但我不知道为什么我会看到多次打印的消息。每条消息都有一个时间戳,当我从设备读取消息时,它会清除缓冲区,因此我知道我没有多次读取该消息。

我在这里遗漏了什么吗?有没有更好的方法来处理这种生产者/消费者场景以确保准确的数据?我已经看过是否有某些地方的引用可能重叠,但我没有看到它。

0 个答案:

没有答案