C#事件处理更新控件出现多线程错误问题求助
多线程环境下UI控件更新错误的解决
问题背景
布局关系:
frmClient 依赖带有 OnLoginEvent 的 frmLogin(LoginWindow)
frmClient接收frmLogin的OnLoginEvent时,可直接更新状态栏(无需使用Invoke)。
但frmClient同时依赖ClassMatrix(简称Matrix),Matrix的ConnectionUpdateEvent由ClassServer的事件触发。当frmClient接收Matrix <- ClassServer这条链路的事件时,更新状态栏会触发多线程错误,具体差异:
frmClient <- frmLogin 的事件:正常更新状态栏 frmClient <- Matrix <- Server 的事件:触发多线程错误
相关代码
Server核心代码
public event EventHandler<string> OnConnectionAcceptedEvent; protected virtual void ConnectionAccepted(string e) { OnConnectionAcceptedEvent?.Invoke(this, e); }
Server业务逻辑代码
Task.Run(new Action(async() =>..... Client = new TcpClient("127.0.0.1", Port); if (ID == 9999) { Guid _guid = Guid.NewGuid(); StageOneReaderServerStarted(ID.ToString()); ConnectionAccepted(ID.ToString()); // 触发事件的代码 GuidCreated(_guid.ToString()); SentGuid(_guid.ToString()); StartedStandardEncryption(ID.ToString()); StageTwoReaderServerStarted(ID.ToString()); }
Matrix代码
public event EventHandler<string> ConnectionUpdateEvent; // 绑定Server的事件 Servers.Servers[Servers.Servers.FindIndex(f => f.ID == id)].OnConnectionAcceptedEvent += Matrix_OnConnectionAcceptedEvent; private void Matrix_OnConnectionAcceptedEvent(object sender, string e) { ConnectionUpdateEvent?.Invoke(this, e); Console.WriteLine($"Connection Accepted: {e}"); }
Client代码
Matrix.ConnectionUpdateEvent += Matrix_ConnectionUpdateEvent; LoginWindow.OnLogin += LoginWindow_OnLoginEvent; // 触发多线程错误的方法 protected virtual void Matrix_ConnectionUpdateEvent(object sender, string e) { statusPanel.Text = "Logging in..."; // 此处报错 } // 正常工作的方法 protected virtual void LoginWindow_OnLoginEvent(object sender, string data) { statusPanel.Text = "Logging in..."; // 正常执行 Matrix.SentLogin(data); }
问题原因
两种事件的线程上下文不同:
- frmLogin的
OnLoginEvent是在UI线程触发的,因此直接更新UI控件不会有问题; - Server的
ConnectionAccepted事件是在Task.Run开启的后台线程中触发的,事件经Matrix传递到frmClient时,依然处于后台线程上下文。而UI控件只能由创建它的线程(即UI线程)修改,跨线程直接操作就会触发错误。
解决方法
在frmClient的事件处理方法中,先判断是否需要切换到UI线程,再更新控件:
修改后的Client事件处理代码:
protected virtual void Matrix_ConnectionUpdateEvent(object sender, string e) { // 判断当前线程是否为UI线程 if (statusPanel.InvokeRequired) { // 切换到UI线程执行更新逻辑 statusPanel.Invoke(new Action(() => { statusPanel.Text = "Logging in..."; })); } else { statusPanel.Text = "Logging in..."; } }
也可以用更简洁的写法:
protected virtual void Matrix_ConnectionUpdateEvent(object sender, string e) { statusPanel.Invoke((Action)(() => { statusPanel.Text = "Logging in..."; })); }
补充说明
InvokeRequired:用来判断当前线程是否是创建控件的线程,返回true则需要切换线程;Invoke:同步切换到UI线程执行委托,会等待UI线程完成操作后再返回;- 如果不需要等待UI操作完成,可使用
BeginInvoke异步切换,但需注意后续逻辑的线程安全。
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

