【问题标题】:Child thread processes event raised by Parent子线程处理由 Parent 引发的事件
【发布时间】:2013-09-11 08:57:18
【问题描述】:

我想要达到的目标与标题相同。在我的问题中,我的主线程中有一个数据列表,我想让主线程触发一个可由子线程处理的事件以使用数据列表。无论如何在C#中可以做到这一点?

我首先想到的方法是让子线程不断检查数据列表。但是我认为通过让主线程通知子线程,我可以节省让子线程在后台不断运行的开销。

【问题讨论】:

标签: c# multithreading events


【解决方案1】:

您可能应该在这里使用生产者-消费者模式。父线程将充当生产者,子线程将充当消费者。父级将生成数据并以这样一种方式发布数据,即通知子级(无需轮询),以便它可以使用它。幸运的是,.NET 使用 BlockingCollection 类使这变得容易。这是您的代码的样子。

class Producer
{
  private BlockingCollection<YourData> queue;

  public Producer(BlockingCollection<YourData> q)
  {
    queue = q;
  }

  public void GenerateItems()
  {
    while (...)
    {
      YourData item = GenerateItem();
      queue.Add(item);
    }
  }
}

class Consumer
{
  public Consumer(BlockingCollection<YourData> queue)
  {
    Task.Factory.StartNew(
      () =>
      {
        foreach (YourData item in queue.GetConsumingEnumerable())
        {
          ProcessItem(item);
        }
      ), TaskCreationOptions.LongRunning);
  }

  private void ProcessItem(YourData item)
  {
    // Add logic to process each data item here.
  }

}

所以我们有一个Producer 类生成数据项并将它们添加到BlockingCollection 和一个Consumer 类在这些数据项可用时删除它们。 GetConsumingEnumerable 的行为类似于 Take,如果队列中没有任何内容,它会将调用者置于空闲状态。所以基本上调用线程没有做任何类型的繁忙轮询(至少不是在微不足道的意义上),所以它是资源友好的。

这是如何将所有内容粘合在一起的示例。

public static void Main()
{
  var queue = new BlockingCollection<YourData>();
  var producer = new Producer(queue);
  var consumer = new Consumer(queue);
  producer.GenerateItems();
}

如果要添加优雅终止,您可以使用task cancellation mechanisms 让GetConsumingEnumerable 枚举器弹出。我没有在这里展示这是怎么做到的,但这并不是很困难,而且已经有很多例子了。

【讨论】:

  • +1 BlockingCollection 使用 GetConsumingEnumerable 函数很好地处理优雅终止。请参阅blog.mischel.com/2013/07/18/simple-multithreading-part-2 的示例。此外,Take 方法都有支持CancellationToken 的重载。所以不需要while(true) 之类的。我认为如果您放弃 ProducerConsumerQueue 类并始终使用 BlockingCollection,您的答案会更清楚。
  • @JimMischel:同意。 ProducerConsumerQueue 位可能会分散重点,当然,无论如何都是完全没有必要的。
  • 这是一个清晰的解释和简洁的示例,说明我可以真正用来解决我所问的问题。我要试试看。我非常感谢您的帮助@Brian Gideon
【解决方案2】:

我会考虑使用TPL DataFlow 中的ActionBlock - 特别是在不需要按顺序处理消息的情况下。作为奖励,您无需在代码中管理使用者线程。

这是一个例子:

 // Create an ActionBlock that performs some work. 
 var workerBlock = new ActionBlock<MessageData>(

  data=>
     {
         DoWork(data); // method consuming data
     },

  // Specify a maximum degree of parallelism. 
  new ExecutionDataflowBlockOptions
  {
     MaxDegreeOfParallelism = maxDegreeOfParallelism
  });

// Post messageCount messages to the queue
for (int i = 0; i < messageCount; i++)
 {
   var messageData = new MessageData();
   workerBlock.Post(messageData);
 }

 // Signal that Producer has no more data to send
 workerBlock.Complete();

 // Wait for all messages to propagate through the network.
 workerBlock.Completion.Wait();

【讨论】:

  • +1:即使 OP 想要按顺序处理消息,仍然应该有一种方法来处理数据流块。也许通过在块上使用SendAsync 方法,然后选择退出并行性。
  • 在我的场景中,生产者不会完成发布消息。相反,消息会被生产者不断地放入队列中,消费者需要定期处理队列中的消息。因此,我将采用 BlockCollection 方法,但我非常感谢您的友好建议。
  • @liudaisuda:请注意,BlockingCollection 也需要在程序关闭之前“完成”以允许消费者退出等待循环。
  • @alexm:感谢您指出这一点。我相信这是为什么我的程序表现得像坐在无限循环中的重要线索。我让我的生产者线程将项目添加到阻塞集合和消费者作为生产者的子线程。你认为让它发挥作用是否可行。我之所以这么问,是因为生产者似乎需要完成使用块集合并在消费者实际处理块集合之前自行关闭,
  • @alexm:顺便说一句,我无法将生产者状态设置为“已完成”。是不是需要关闭生产者程序才能设置完成?
【解决方案3】:

ObservableCollection 模板类可能是您的问题的解决方案:

http://msdn.microsoft.com/en-us/library/ms668604.aspx

表示一个动态数据集合,它在何时提供通知 添加、删除或刷新整个列表时。

【讨论】:

    【解决方案4】:

    当主线程准备好使用数据时,一个非常简单的方法是让您的主线程简单地生成一个带有数据副本的线程池线程。无需发信号;不需要已经运行后台线程。

    【讨论】:

    • 您的解决方案在我的情况下非常适用,因为我的生产者可以说会不断地将数据添加到队列中。因此,不存在数据准备好使用的点。不过感谢您的帮助
    • 啊。当你说“通知子线程”时,我认为有这样的观点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-27
    相关资源
    最近更新 更多