【问题标题】:Need help setting up threads/background worker in GUI需要帮助在 GUI 中设置线程/后台工作人员
【发布时间】:2012-10-29 18:28:58
【问题描述】:

我在 Visual Studio 2010 中使用 C# 和 Winforms

我有一个程序,我试图通过串行端口读取输出并将其打印到屏幕上。它最初是作为一个控制台程序开始的,但现在已经发展到我们希望将输出放在表单上的字段中。我有代码可以解析出我正在寻找的输出的串行端口写入和工作,我只需将 Console.WriteLine 更改为 label.text = "";,基本上。我已将监听串口的函数合并到 GUI 代码中,所以所有内容都在同一个文件中。

不过,我对如何让函数写入标签感到困惑。它是静态的,所以我不能只说'label.text ='。我尝试在要使用的函数内创建一个新的表单对象,这允许我访问表单上的控件,但没有更新我在运行时看到的表单(我猜是因为我创建了表单的新实例而不是访问现有实例?)

我还需要让串行侦听器与 GUI 同时运行,因此 GUI 标签将更新为接近实时运行该函数所获得的结果,所以我尝试设置它要线程化,GUI 是由 main() 启动的一个线程,串行侦听器是另一个线程,当我单击按钮启动它时启动它。但是,我遇到了同样的问题,无法访问串行侦听器线程中的标签,因为它必须是静态的才能使用 system.threading 进行初始化。

我在想也许我需要为串行侦听器使用后台工作人员,但我对这些的经验绝对为零。后台工作人员能否实时更新 GUI 上的标签?

我不能发布具体的代码,但这是大致的想法:

Main() 启动 GUIthread

GUI 具有启动串行侦听器的按钮 OnClick 按钮启动 ListenerThread ListenerThread 输出到控制台,希望输出到表单标签

无法访问 GUI.Label,因为 Listener 是静态的,无需线程化 在 Listener 中创建新的 GUI 实例允许我调用该实例的控件,但它们不会在运行时更新 GUI

确保标签是公开的。

【问题讨论】:

  • 看到更新标签时出现的错误吗?
  • 很遗憾没有,但是如果您对某事有特定的疑问,我可以告诉您或解释一下。
  • 我能够成功编译的唯一方法是在侦听器函数中创建 GUI 表单的新实例 (testgui gui = new testgui();),然后通过说 gui 访问标签。标签,虽然这不起作用,因为我猜这算作 gui 的一个“不同”实例,而不是我启动应用程序时启动的那个实例。不过,我可能是不正确的
  • @JoshBibb 你检查标签修饰符了吗?您无法引用标签吗?
  • 我已确保标签是公开的。我无法访问它,因为我试图从静态侦听器方法访问它,这是非法的。而且我不能使该函数成为非静态函数,否则我将无法使用它设置线程

标签: c# multithreading user-interface


【解决方案1】:

BackgroundWorker 类本质上就是为此而生的。

只需让DoWork 方法完成您的实际工作,并确保在工作时根据需要调用ReportProgess。您可以将任何数据作为string(或其他任何内容,如果您愿意)传递,然后在ProgressChanged 事件处理程序中使用该值,表单可以处理该值来更新其 UI。

请注意,BackgroundWorker 将自动确保 ProgressChangedRunWorkerCompleted 事件在 UI 线程中运行,因此您无需为此烦恼。

这是一个示例工人:

public class MyWorker//TODO give better name
{
    public void DoWork(BackgroundWorker worker)//TODO give better name
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(1000);//to mimic real work
            worker.ReportProgress(0, i.ToString());
        }
    }
}

这是一个配置后台工作人员的示例。在这里,我使用 lambdas 是因为能够关闭变量很方便(即在每个匿名方法中使用变量),但是如果您愿意,可以将每个事件处理程序重构为方法。

private void button1_Click(object sender, EventArgs e)
{
    var bgw = new BackgroundWorker();
    MyWorker worker = new MyWorker();

    bgw.WorkerReportsProgress = true;
    bgw.DoWork += (s, args) => { worker.DoWork(bgw); };
    bgw.ProgressChanged += (s, data) =>
    {
        label1.Text = data.UserState.ToString();
    };
    bgw.RunWorkerCompleted += (s, args) =>
    {
        label1.Text = "All Done!";
    };

    bgw.RunWorkerAsync();//actually start the worker
}

请注意,表单中的所有控件都不是公共的,它们都不是静态的,并且我没有在类之外传递对我的表单的任何引用。它被认为是每个Form 负责更新其自己的控件的最佳形式。您不应该允许其他任何人直接访问它们。与其允许其他工作人员类直接访问标签或修改其文本,工作人员只是简单地告诉表单,“嘿,我有一些数据,你可以根据这些值相应地更新自己。 "然后是负责更新自身的表单。事件是您用来允许这些工作人员或其他类型的子元素(例如您创建的其他表单)通知“父”表单它需要更新自身的东西。

【讨论】:

  • 我认为这是让我走上后台工作路径的一个很好的开始,非常感谢!
【解决方案2】:

要写入任何 Windows 控件,您必须在 UI 线程上。如果您有一个在不同线程上运行的串行侦听器,那么您需要在更改窗口控件之前切换线程。 BeginInvoke 可以很方便,http://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke.aspx

我要做的是向串行侦听器添加一个 Action,每当侦听器想要显示某些内容时就会调用它。然后这个 Action 会调用 BeginInvoke。

类似:

static class SerialListner
{
    public Action<string> SomethingToDisplay;

    void GotSomethingToDisplay(string s)
    {
        SomethingToDisplay(s);
}

然后在你的窗体中某处

SerialListern.SomethingToDisplay = (s) => 
   label.BeginInvoke((Action) () => label.Text = s);

【讨论】:

    【解决方案3】:

    我认为你可以使用后台工作程序,而且它们真的很容易使用。

    为了使用 BackgroundWorker,您必须至少实现两个事件:

    backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    

    在那里您阅读了您的输入。它被触发调用 backgroundWorker1.RunWorkerAsync(...)

    backgroundWorker1_ProgressChanged(....)
    

    在那里你更新你的标签。也许你必须创建一个委托来更新它。

    你也可以实现:

    backgroundWorker1_RunWorkerCompleted(....)
    

    让你知道它什么时候停止...

    【讨论】:

      【解决方案4】:

      继续您所说的静态侦听器方法并且它曾经是一个控制台应用程序,我认为一个相对较小的修改可能如下:

      class Program
      {
          static void Main(string[] args)
          {
              // Create a main window GUI
              Form1 form1 = new Form1();
      
              // Create a thread to listen concurrently to the GUI thread
              Thread listenerThread = new Thread(new ParameterizedThreadStart(Listener));
              listenerThread.IsBackground = true;
              listenerThread.Start(form1);
      
              // Run the form
              System.Windows.Forms.Application.Run(form1);
          }
      
          static void Listener(object formObject)
          {
              Form1 form = (Form1)formObject;
      
              // Do whatever we need to do
              while (true)
              {
                  Thread.Sleep(1000);
                  form.AddLineToTextBox("Hello");
              }
          }
      }
      

      在这种情况下,Form1 显然是表单类,Listener 是监听方法。这里的关键是我将表单对象作为参数传递给 Listen 方法(通过 Thread.Start),以便侦听器可以访问 GUI 的非静态成员。请注意,我已将 Form1.AddLineToTextBox 定义为:

      public void AddLineToTextBox(string line)
      {
          if (textBox1.InvokeRequired)
              textBox1.Invoke(new Action(() => { textBox1.Text += line + Environment.NewLine; }));
          else
              textBox1.Text += line + Environment.NewLine;
      }
      

      特别注意,由于现在Listener 方法在单独的线程中运行,您需要在GUI 控件上使用Invoke 方法进行更改。我在这里使用了一个 lambda 表达式,但如果你的目标是 .net 的早期版本,你可以很容易地使用一个完整的方法。请注意,我的 textBox1 是 TextBox,其中 Multiline 设置为 true,ReadOnly 设置为 false(类似于标签)。

      另一种可能需要更多工作但可能更优雅的架构是执行相反的依赖关系:您使用对侦听器对象的引用来创建表单。然后,监听器将引发 GUI 订阅的事件,以更新其显示。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-12-18
        • 2014-08-14
        • 2021-08-08
        • 1970-01-01
        • 1970-01-01
        • 2023-03-24
        • 1970-01-01
        相关资源
        最近更新 更多