【问题标题】:Consumer/producer locking GUI thread消费者/生产者锁定 GUI 线程
【发布时间】:2018-05-22 08:20:22
【问题描述】:

我制作了一个示例消费者/生产者线程应用程序,因此我可以学习如何正确使用它。

我希望它允许一个线程向 GUI 线程发送命令,用内容更新 GUI。

我可以正常工作,但有一个小问题。 GUI 线程是我的消费者线程,所以我总是让它检查新命令(使用 while 循环)。问题是由于这个 while 循环,GUI 永远不会显示,因为它总是卡在 while 循环中。请注意,字符串 Queue 最终将被替换为更复杂的对象(包含数据和命令类型的对象)。

我不确定如何让 GUI 线程在不中断 GUI 功能的情况下使用命令。我做错了吗?

这是我的 Form1.cs 代码(该表单仅包含 1 个 RichTextBox 用于显示名为 OutputBox 的输出)。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace MultiThreading
{
   class ThreadCommandQueue
   {
      public static ThreadCommandQueue instance = new ThreadCommandQueue();

      private Queue<string> m_queue;
      private Object m_lock;

      public static ThreadCommandQueue GetInstance()
      {
         return instance;
      }

      private ThreadCommandQueue()
      {
         m_queue = new Queue<string>();
         m_lock = new Object();
      }

      public void Add(
         string data_to_add)
      {
         lock (m_lock)
         {
            m_queue.Enqueue(data_to_add);
         }
      }

      public string Get()
      {
         lock (m_lock)
         {
            if (m_queue.Count > 0)
            {
               return m_queue.Dequeue();
            }

            return null;
         }
      }
   }

   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }

      private void PopulateThreadCommandQueue()
      {
         int i = 0;
         while(true)
         {
            ThreadCommandQueue.GetInstance().Add("Item #: " + i.ToString());
            i++;
         }
      }

      private void Form1_Load(object sender, EventArgs e)
      {
         // Create the Command Queue....
         ThreadCommandQueue.GetInstance();

         // Create a Testing Producer Thread
         Thread ProducerThread = new Thread(PopulateThreadCommandQueue);
         ProducerThread.Start();

         // The GUI thread is the Consumer, so keep checking the CommandQueue for data...
         while(true)
         {
            string latest = ThreadCommandQueue.GetInstance().Get();
            if(latest != null)
            {
               OutputBox.Text += latest + "\n";
            }
         }
      }
   }
}

【问题讨论】:

  • 不清楚你在问什么。如果练习的目的是显式实现消费者,则该对象必须在 UI 线程之外的线程中执行。就那么简单。对于任何事情,您都不能阻止 UI 线程。但是,UI 线程本身就是一种消费者线程;它将执行您传递给Control.Invoke()Control.BeginInvoke() 方法的任何委托。这种隐式使用已经存在的 UI 线程使用者实现将适用于大多数实际场景,并且可能适用于您的情况。但是您的问题根本不够清楚,无法知道
  • 尝试使用 BlockingCollection,在您的代码中读取时会浪费 CPU。当然,在某些情况下 spinwait 被占用,但不是在这个。

标签: c# multithreading user-interface


【解决方案1】:

使用并发队列。因此从队列中添加和获取不需要锁定。

此外,您不会实时从 UI 线程(while 循环)连续接收命令。如果您有这种情况,请使用单独的线程来接收结果。

然后,您可以从接收者线程中使用 Invoke 命令更新 UI,如下所示。

//This method called from receiver thread 
public void UpdateForm(Data d) 
{
   if(this.InvokeRequired) 
   {
      this.Invoke(new MethodInvoker(() => this.UpdateFormUI(r)));
   }
   else 
   {
      this.UpdateFormUI(data)
   }
}

public void UpdateFormUI(Data d)
{
   //Does actual ui update
}

【讨论】:

  • 不,它不是线程安全的。文档说“如果您需要同时从多个线程访问集合,请使用 ConcurrentQueue 或 ConcurrentStack。”
  • 太棒了,谢谢!你让我看起来除了使用 while 循环之外还有另一个选择。有没有办法让我只在有可用命令时尝试读取命令?
【解决方案2】:

好的,这里的问题是您需要一直轮询消息循环以使 GUI 正常工作,并且您还需要一直轮询 IPC 命令队列以使命令正常工作,并且您需要在以下时间进行轮询同一时间在同一线程上。

有多种方法可以解决此问题,但最简单的方法是处理消息循环,当没有要处理的内容时,执行 IPC 命令队列处理。对于 WinForms 应用程序,这将类似于:

public Form1()
{
   InitializeComponent();
   Application.Idle += (sender, eargs) => ProcessCommands();
}

private void ProcessCommands()
{
    while(true)
    {
      string latest = ThreadCommandQueue.GetInstance().Get();
      if(string.IsNullOrEmpty(latest)) return;
      OutputBox.Text += latest + "\n";
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-06
    • 2018-09-24
    • 2017-02-01
    • 2012-04-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多