在监听COM端口时,跨线程操作无效。

8
我正在尝试监听COM端口,以便为SerialPort.DataReceived事件创建新的处理程序。逻辑很简单-我在TextBox1中写入一些内容,按下Button1,我的文本应该显示在Label1中。但是我的应用程序不想运行,因为它会抛出“跨线程操作无效”的错误。 我进行了一些搜索,并找到了Invoke对象-我如何在我的示例中使用它?为什么需要包含Invoke逻辑?
namespace WindowsApplication1
{
public partial class Form1 : Form
{
    SerialPort sp = new SerialPort();

    public Form1()
    {
        InitializeComponent();
        sp.DataReceived += MyDataReceivedHandler;
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

    private void MyDataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
    {
        try
        {
            //sp.PortName = "COM3";
            //sp.Open();
            Label1.Text = sp.ReadLine();
        }
        catch (Exception exception)
        {
            RichTextBox1.Text = exception.Message + "\n\n" + exception.Data;
        }
        finally
        {
            sp.Close();
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        try
        {
            sp.PortName = "COM3";
            sp.Open();
            sp.WriteLine(TextBox1.Text);
        }
        catch (Exception exception)
        {
            RichTextBox1.Text = exception.Message + "\n\n" + exception.Data;
        }
        finally
        {
            sp.Close();
        }
    }
}

}


@ Peter:这里的COM端口是RS232串行接口。尽管有USB和其他接口,但仍然有许多设备(例如GPS、医疗设备)使用串行端口进行PC通信。 - Sesh
@simon: 只是好奇:在这个特定的应用程序中,COM端口有什么用途? - Peter Mortensen
2个回答

19

我猜测 MyDataReceivedHandler 在与GUI不同的线程上运行。为了解决这个问题,你需要在正确的线程上调用Text属性设置器。以下是一个示例:

public void SetControlText(Control control, string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action<Control,string>(SetControlText), new object[] { control, text });
    }
    else
    {
        control.Text = text;
    }
}

private void MyDataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        //sp.PortName = "COM3";
        //sp.Open();
        SetControlText(Label1, sp.ReadLine());
    }
    catch (Exception exception)
    {
        SetControlText(RichTextBox1, exception.Message + "\n\n" + exception.Data);
    }
    finally
    {
        sp.Close();
    }
}

如果你在使用 .NET Framework 2.0,那么上述的 Action<T1, T2> 委托是不可用的,因此你需要定义自己的委托:

private delegate void SetControlTextHandler(Control control, string text);

public void SetControlText(Control control, string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new SetControlTextHandler(SetControlText), new object[] { control, text });
    }
    else
    {
        control.Text = text;
    }
}

SetControlText 方法可以像这样缩短(甚至可以静态):(在2.0和3.5中均适用):

public static void SetControlText(Control control, string text)
{
    ´control.Invoke((MethodInvoker)delegate { control.Text = text; });
}

那么您就不需要每次都检查InvokeRequired,但反过来,即使不需要,您也会将调用包装在委托中。我认为,在这样的GUI方法中,这两种方法之间的任何性能差异都是可以忽略不计的,因此我倾向于使用更短的形式,因为它写起来代码更少。


看起来,这只适用于3.5版本。我使用的是Visual Studio 2005,现在我安装了3.5 SP1。在Visual Studio 2005中,我应该在哪里设置我正在使用的.NET框架? - sventevit
@simon: 我已经更新了答案,加入了2.0兼容版本。 - Fredrik Mörk
注意:如果委托中执行的操作是长时间运行的,则仍然可能会阻塞UI,因为调用将导致UI线程处理操作。对于所有实现它的控件使用BeginInvoke将异步执行操作,而不会阻塞。 - Steven Evers
如果您不在控件上调用invoke/begininvoke,而是在“this”(窗体)上调用,那么就会出现+1的问题。 - Steven Evers

1

每当从不同于创建它的线程访问UI控件时,您也可以执行以下操作:

(.NET 3.5)

myControl.BeginInvoke(new MethodInvoker( () => myControl.whatever = whatever; ));

或者(.NET 2.0)

myControl.BeginInvoke(new MethodInvoker( delegate { myControl.whatever = whatever; ));

编辑> 有时候在长时间运行的操作中使用Invoke可能会导致界面卡住,而使用BeginInvoke则可以明显地以异步方式执行该操作,从而避免了界面卡顿。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接