关于winform串口接收事件 跨线程更新问题

62 阅读2分钟
using System;
using System.IO.Ports;
using System.Windows.Forms;
using System.Threading;

public class SerialPortForm : Form
{
    private SerialPort _serialPort;
    private TextBox _textBox;

    public SerialPortForm()
    {
        _textBox = new TextBox { Multiline = true, Dock = DockStyle.Fill };
        Controls.Add(_textBox);

        _serialPort = new SerialPort("COM1", 9600);
        _serialPort.DataReceived += SerialPort_DataReceived;
        _serialPort.Open();
    }

    private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string data = _serialPort.ReadExisting();
        UpdateTextBox(data);
    }

    private void UpdateTextBox(string text)
    {
        if (_textBox.InvokeRequired)
        {
            _textBox.BeginInvoke(new Action<string>(UpdateTextBox), text);
        }
        else
        {
            _textBox.AppendText(text);
        }
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _serialPort.Close();
        base.OnFormClosing(e);
    }

    [STAThread]
    static void Main()
    {
        Application.Run(new SerialPortForm());
    }
}


Invoke和BeginInvoke是WinForms中用于跨线程更新UI的核心方法,主要区别体现在执行方式和线程行为上:

执行方式差异‌

Invoke:同步调用,会阻塞当前线程直到UI线程完成委托执行 BeginInvoke:异步调用,立即将委托加入UI线程消息队列后立即返回,不阻塞当前线程

线程行为对比‌

特性 Invoke BeginInvoke 线程阻塞 调用线程被阻塞直到UI操作完成 调用线程继续执行不等待 异常处理 异常直接抛给调用线程 异常需通过EndInvoke捕获 返回值 直接返回委托结果 返回IAsyncResult对象

典型应用场景‌

Invoke适用于需要确保UI更新完成后才能继续执行的逻辑,如串口数据接收后必须立即显示的场合 BeginInvoke更适合高频UI更新(如实时曲线绘制),避免因同步等待导致数据堆积

示例代码演示两种调用方式:

csharp Copy Code // 同步调用示例 _textBox.Invoke(new Action(s => _textBox.Text = s), "Text1");

// 异步调用示例 var asyncResult = _textBox.BeginInvoke(new Action(s => _textBox.Text = s), "Text2"); _textBox.EndInvoke(asyncResult); // 可选结束调用

实际开发中,高频数据场景建议优先使用BeginInvoke以避免界面卡顿,而需要严格顺序执行的逻辑则应选择Invoke。两者均通过检查InvokeRequired属性确保线程安全。

    
using System;
using System.Windows.Forms;
using System.IO.Ports;

public class SerialPortForm : Form
{
    private TextBox _textBox;
    private SerialPort _serialPort;

    public SerialPortForm()
    {
        _textBox = new TextBox { Multiline = true, Dock = DockStyle.Fill };
        Controls.Add(_textBox);

        _serialPort = new SerialPort("COM1", 9600);
        _serialPort.DataReceived += SerialPort_DataReceived;
        _serialPort.Open();
    }

    private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string data = _serialPort.ReadExisting();
        IAsyncResult result = _textBox.BeginInvoke(new Action<string>(UpdateTextBox), data);
        
        // 如果需要等待UI更新完成(非必须)
        _textBox.EndInvoke(result); // 这里会阻塞当前线程直到UI操作完成
    }

    private void UpdateTextBox(string text)
    {
        _textBox.AppendText(text);
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _serialPort.Close();
        base.OnFormClosing(e);
    }

    [STAThread]
    static void Main()
    {
        Application.Run(new SerialPortForm());
    }
}