簡體   English   中英

需要幫助在GUI中設置線程/后台工作程序

[英]Need help setting up threads/background worker in GUI

我在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

確保標簽是公開的。

BackgroundWorker類基本上是為此而制作的。

只需讓DoWork方法完成您的實際工作,並確保在根據需要工作時調用ReportProgess 您可以將任何數據作為string (或其他任何內容,如果需要)傳遞,然后在ProgressChanged事件處理程序中使用該值,表單可以處理該值以更新其UI。

請注意, BackgroundWorker將自動確保在UI線程中運行ProgressChangedRunWorkerCompleted事件,因此您無需為此煩惱。

這是一個樣本工作者:

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最好是負責更新自己的控件。 您不應該允許任何其他人直接訪問它們。 工作人員只是在告訴表單,“嘿,我有一些數據,你可以根據這些值相應地更新自己,而不是允許其他工人類直接訪問標簽或修改它的文本。” “ 然后是負責更新自身的表單。 事件是您用來允許這些工作者或其他類型的子元素(例如您創建的其他表單)通知“父”表單需要更新自身的事件。

要寫入任何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);

我認為你可以使用后台工作者,它們非常容易使用。

要使用BackgroundWorker,您必須至少實現兩個事件:

backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

在那里你讀了你的輸入。 它觸發了調用backgroundWorker1.RunWorkerAsync(...)

backgroundWorker1_ProgressChanged(....)

你在那里更新你的標簽。 也許你必須創建一個委托來更新它。

你也可以實現:

backgroundWorker1_RunWorkerCompleted(....)

讓它知道什么時候停止......

繼續你所說的靜態監聽器方法以及它曾經是一個控制台應用程序,我認為相對較小的修改可能如下:

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是一個TextBoxMultiline設置為true, ReadOnly設置為false(類似於標簽)。

另一種可能需要更多工作但可能更優雅的架構是執行相反的依賴關系:您使用對Listener 對象的引用來創建表單。 然后,監聽器將引發GUI將訂閱的事件以便更新其顯示。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM