You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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属性,也可以在后台线程里通过DispatcherClose()操作派发到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

火山引擎 最新活动