在Windows窗体应用程序中加入工作线程

时间:2017-04-25 18:11:08

标签: c# multithreading forms

我的程序是这样的: 我按下一个打开端口的单选按钮 接下来我按下按钮"阅读"它启动一个线程,使用port.ReadLine()从串行端口连续读取数据并将其打印在文本框中;
我有另一个收音机应首先加入线程,然后关闭端口;问题是打印顺利,直到我关闭端口时UI冻结。

public Form1()
        {
            mythread = new Thread(ReadFct);
            myPort = new SerialPort("COM3", 9600);
            myPort.ReadTimeout = 3500;
            InitializeComponent();
            foreach (var t in Constants.ComboParameters)
                this.paramCombo.Items.Add(t);
            radioClose.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
            radioOpen.CheckedChanged += new EventHandler(radioButtonCheckedChanged);


        }

以下是附加到帖子的功能

void ReadFct()
        {
            string aux = "";
            while (readCondition)
            {
                if (myPort.IsOpen)

                    aux = myPort.ReadLine();


                this.SetText(aux);
            }

        }

下面是单选按钮事件处理程序

 public  void radioButtonCheckedChanged(object sender,EventArgs e)
            {
                if (radioOpen.Checked && !myPort.IsOpen)
                    try
                    {

                        myPort.Open();
                        mythread.Start();
                    }
                    catch (Exception)
                    {
                        MessageBox.Show("Nu s-a putut deschide port-ul");
                    }

                if (radioClose.Checked && myPort.IsOpen)
                {
                    readCondition = false;
                    mythread.Join();

                    myPort.Close();

                    //  myPort.DataReceived -= DataReceivedHandler;

                }


 }

读取按钮功能:

 private void readbtn_Click(object sender, EventArgs e)
        {

            if (!myPort.IsOpen)
                MessageBox.Show("PORT NOT OPENED!");
            else
            {
                // myPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
                readCondition = true;
                if (!mythread.IsAlive)
                {
                    mythread = new Thread(ReadFct);
                    mythread.Start();
                }

            }

我使用了MSDN从另一个线程更改控件时建议的内容:

private void SetText(string text)
        {
            if (this.textBox1.InvokeRequired)
            {
                StringTb del = new StringTb(SetText);
                this.Invoke(del, new object[] { text });
            }
            else
                SetData = text;

        }

1 个答案:

答案 0 :(得分:2)

很难确切地知道你需要什么,缺乏一个好的Minimal, Complete, and Verifiable code example来说明问题。也就是说,这里的问题是Thread.Join()方法导致该线程停止执行任何其他工作,并且用于调用该方法的线程是处理所有用户界面的线程。更糟糕的是,如果您的端口永远不会收到另一个换行符,那么您等待的主题将永远不会终止,因为您等待ReadLine()方法。更糟糕的是,即使你确实得到换行符,如果你在等待Thread.Join()时发生了这种情况,那么对Invoke()的调用将会死锁,因为它需要UI线程才能执行工作,Thread.Join()调用阻止它获取UI线程。

换句话说,您的代码存在多个问题,其中任何一个问题都可能导致问题,但所有问题都意味着它可能无法正常工作。

有很多策略可以解决这个问题,但恕我直言,最好是使用await。这样做的第一步是改变您的I / O处理,使其异步完成,而不是专门给它一个线程:

// Ideally, you should rename this method to "ReadFctAsync". I am leaving
// all names intact for the same of the example though.

async Task ReadFct()
{
    string aux = "";
    using (StreamReader reader = new StreamReader(myPort.BaseStream))
    {
        while (true)
        {
            aux = await reader.ReadLineAsync();

            // This will automatically work, because the "await" will automatically
            // resume the method execution in the UI thread where you need it.
            this.SetText(aux);
        }
    }
}

然后,不是显式创建一个线程,而是通过调用上面的方法创建一个Task对象:

public Form1()
{
    // In this approach, you can get rid of the "mythread" field altogether
    myPort = new SerialPort("COM3", 9600);
    myPort.ReadTimeout = 3500;
    InitializeComponent();
    foreach (var t in Constants.ComboParameters)
        this.paramCombo.Items.Add(t);
    radioClose.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
    radioOpen.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
}

public async void radioButtonCheckedChanged(object sender,EventArgs e)
{
    if (radioOpen.Checked && !myPort.IsOpen)
    {
        try
        {
            myPort.Open();
            await ReadFct();
            // Execution of this method will resume after the ReadFct() task
            // has completed. Which it will do only on throwing an exception.
            // This code doesn't have any continuation after the "await", except
            // to handle that exception.
        }
        catch (Exception)
        {
            // This block will catch the exception thrown when the port is
            // closed. NOTE: you should not catch "Exception". Figure out what
            // *specific* exceptions you expect to happen and which you can
            // handle gracefully. Any other exception can mean big trouble,
            // and doing anything other than logging and terminating the process
            // can lead to data corruption or other undesirable behavior from
            // the program.
            MessageBox.Show("Nu s-a putut deschide port-ul");
        }

        // Return here. We don't want the rest of the code executing after the
        // continuation, because the radio button state might have changed
        // by then, and we really only want this call to do work for the button
        // that was selected when the method was first called. Note that it
        // is probably even better if you just break this into two different
        // event handlers, one for each button that might be checked.
        return;
    }

    if (radioClose.Checked && myPort.IsOpen)
    {
        // Closing the port should cause `ReadLineAsync()` to throw an
        // exception, which will terminate the read loop and the ReadFct()
        // task
        myPort.Close();
    }
}

在上面,我完全忽略了readbtn_Click()方法。由于缺乏一个好的MCVE,它不清楚该按钮在整个方案中扮演的角色。您似乎有一个单选按钮组(两个按钮),用于控制端口是打开还是关闭。目前尚不清楚为什么你有一个额外的常规按钮,似乎也可以打开端口并开始阅读,独立于无线电组。

如果您想要这个额外的按钮,在我看来它应该做的就是通过检查"打开"来改变无线电组状态。单选按钮。然后让无线电组按钮处理端口状态和读取。如果您需要有关如何将上面的代码示例与整个UI完全集成的更具体建议,则需要提供更多详细信息,最好是在新问题中。这个新问题必须包括一个好的MCVE。