如何在Task中关闭WPF窗口,避免线程异常且不冻结界面
解决Task线程中关闭窗口引发的InvalidOperationException问题
这个问题我太熟了——WPF(以及绝大多数UI框架)都有个铁律:所有UI相关的操作必须在创建它的UI线程执行,后台线程绝对不能直接操作UI对象。你代码里的Task.Run是把连接检查放到了后台线程,最后直接在这个线程里调用this.Close(),这就属于跨线程操作UI,自然触发了那个异常。
下面给你两种靠谱的解决方案,既能保证进度条正常旋转(UI不冻结),又能安全关闭窗口:
方案一:用async/await(最推荐,代码最简洁)
把事件处理方法改成async类型,用await等待后台任务完成,这样await之后的代码会自动回到UI线程,直接调用Close()就没问题了:
private async void Window_ContentRendered(object sender, System.EventArgs e) { ConnectionState = false; // 把耗时的连接检查放到后台线程,await会释放UI线程,进度条能正常旋转 var connectionResult = await Task.Run(() => NetworkTools.CheckGlobalConnection()); if (connectionResult == (ConnectionStatus.NetworkConnectionSuccess, ConnectionStatus.ServerConnectionSuccess)) { ConnectionState = true; } // 这里已经回到UI线程了,放心调用Close this.Close(); }
这种方式的好处是完全不用手动处理线程切换,await会帮你搞定上下文切换,代码逻辑清晰,还能保证UI线程在后台任务执行期间不被阻塞,进度条的动画自然能正常跑。
方案二:用Dispatcher手动切回UI线程
如果不想改方法的async属性,也可以在后台线程里通过Dispatcher把Close()操作派发到UI线程执行:
private void Window_ContentRendered(object sender, System.EventArgs e) { Task.Run(() => { ConnectionState = false; if (NetworkTools.CheckGlobalConnection() == (ConnectionStatus.NetworkConnectionSuccess, ConnectionStatus.ServerConnectionSuccess)) { ConnectionState = true; } // 通过Dispatcher把Close操作提交到UI线程执行 this.Dispatcher.Invoke(() => this.Close()); }); }
这里的Dispatcher.Invoke()会等待UI线程执行完Close()再继续,如果你不需要等待,也可以用Dispatcher.BeginInvoke(),不过关闭窗口这种操作用Invoke更稳妥。
额外提醒
顺便说一句:如果你的ConnectionState是绑定到UI的属性(比如用来更新界面状态),那最好也在UI线程里赋值。用方案一的话,await之后的代码本来就在UI线程,直接赋值没问题;用方案二的话,赋值ConnectionState的操作也需要放到Dispatcher.Invoke()里,避免潜在的跨线程问题。
内容的提问来源于stack exchange,提问作者Eros Guil




