仅在事件内调用事件触发方法时出现跨线程操作错误求助
解决SerialPort DataReceived事件中跨线程访问WinForms控件的异常
问题原因
SerialPort的DataReceived事件是在后台线程中触发的,而WinForms控件(比如LogTextBox)只能由创建它的UI线程访问和修改,跨线程操作必然抛出System.InvalidOperationException。而DoAThing方法因为用了await,后续代码会自动回到UI线程执行,所以日志能正常写入。
解决方案
以下是几种实用的解决方式,按需选择:
方法1:在日志事件处理中直接判断并切换线程
修改主窗体中的事件处理代码,通过Invoke切换到UI线程操作控件:
Libby.Log += (sender, e) => { // 检查是否需要跨线程调用 if (LogTextBox.InvokeRequired) { // 委托给UI线程执行操作 LogTextBox.Invoke(new Action(() => { LogTextBox.AppendText($@"{DateTime.Now:hh\:mm\:ss\.ffff}: {e.Message}{Environment.NewLine}"); })); } else { // 已在UI线程,直接操作 LogTextBox.AppendText($@"{DateTime.Now:hh\:mm\:ss\.ffff}: {e.Message}{Environment.NewLine}"); } };
方法2:封装通用UI线程操作方法
在窗体类中封装一个通用方法,简化重复代码:
// 窗体类内新增方法 private void RunOnUiThread(Action action) { if (InvokeRequired) { Invoke(action); } else { action(); } }
然后日志事件处理可以简化为:
Libby.Log += (sender, e) => { RunOnUiThread(() => { LogTextBox.AppendText($@"{DateTime.Now:hh\:mm\:ss\.ffff}: {e.Message}{Environment.NewLine}"); }); };
方法3:在类库中统一切换到UI线程触发事件(可选)
如果不想在窗体端处理跨线程逻辑,可以在类库中捕获UI线程上下文,确保日志事件在UI线程触发:
修改类库代码:
public class LibraryWithLoggingFunctionInIt { SerialPort Port = new SerialPort(/* Port Parameters */); private readonly SynchronizationContext _uiSyncContext; public LibraryWithLoggingFunctionInIt() { // 捕获创建实例时的线程上下文(需确保实例在UI线程创建,比如窗体构造函数中) _uiSyncContext = SynchronizationContext.Current ?? new SynchronizationContext(); Port.DataReceived += Port_DataReceived; } // 其他方法保持不变... public void Logger(string message) { // 在UI线程上下文触发日志事件 _uiSyncContext.Post(_ => Log?.Invoke(this, new LoggerMessageArgs(message)), null); } }
这种方式下,窗体端的日志事件处理可以保持原样,无需额外判断线程。
验证
修改完成后,当SerialPort触发DataReceived事件时,日志会通过UI线程写入LogTextBox,不会再抛出异常,能正常记录"I received new data!"。
内容的提问来源于stack exchange,提问作者Cameron Hadland
相关产品推荐
相关产品推荐

