需要帮助在GUI中设置线程/后台工作程序

时间:2012-10-29 18:28:58

标签: c# multithreading user-interface

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

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

尽管如此,我还是想知道如何让函数写入标签。它是STATIC所以我不能说'label.text ='。我尝试在要使用的函数中创建一个新的表单对象,这允许我访问表单上的控件,但是没有更新我在运行时看到的表单(我猜是因为我已经创建了一个新的表单实例而不是访问现有的实例?)

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

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

我无法发布具体的代码,但这是一般的想法:

Main()启动GUIthread

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

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

确保标签是公开的。

4 个答案:

答案 0 :(得分:2)

BackgroundWorker课程基本上是为此而制作的。

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

请注意,BackgroundWorker会自动确保在{@ 1}}和ProgressChanged事件在UI线程中运行,因此您无需为此烦恼。

这是一个样本工作者:

RunWorkerCompleted

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

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());
        }
    }
}

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

答案 1 :(得分:0)

要写入任何Windows控件,您必须位于UI线程上。如果在不同的线程上运行串行侦听器,则需要在更改Windows控件之前切换线程。 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);

答案 2 :(得分:0)

我认为你可以使用后台工作者,它们非常容易使用。

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

backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

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

backgroundWorker1_ProgressChanged(....)

你有更新你的标签。也许你必须创建一个代理来更新它。

你也可以实施:

backgroundWorker1_RunWorkerCompleted(....)

告诉你何时停止...

答案 3 :(得分:0)

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

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(与标签类似)。

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