在 C# WPF 应用程序中,当你尝试从非 UI 线程(比如从 Task
或 Thread
中)打开或更新窗体时,会遇到 System.InvalidOperationException
,因为 WPF 的 UI 元素(包括窗体)不是线程安全的。只有创建窗体的线程(通常是主 UI 线程)才能安全地访问和修改这些元素。
为了解决这个问题,你需要确保所有对 WPF 窗体的访问都在 UI 线程上进行。如果你正在使用 Task
来打开窗体,你可以通过以下几种方式之一来实现这一点:
1. 使用 Application.Current.Dispatcher.Invoke
或 Dispatcher.Invoke
你可以使用窗体的 Dispatcher
属性(它继承自 DependencyObject
)或 Application.Current.Dispatcher
来在 UI 线程上执行打开窗体的代码。但是,如果你正在非 UI 线程中,并且没有直接访问窗体的实例,那么使用 Application.Current.Dispatcher
会更合适。
Task.Run(() =>
{
// 模拟一些后台工作
System.Threading.Thread.Sleep(1000); // 等待一秒作为示例
// 在 UI 线程上打开窗体
Application.Current.Dispatcher.Invoke(() =>
{
MyWindow window = new MyWindow();
window.Show();
});
});
2. 使用 TaskScheduler.FromCurrentSynchronizationContext()
如果你正在使用 async
和 await
,并且想要从后台任务返回到 UI 线程,你可以使用 TaskScheduler.FromCurrentSynchronizationContext()
来捕获当前 UI 线程的上下文,并在该上下文中安排任务的执行。
但是,请注意,这种方法通常用于已经处于 UI 线程中的代码,并且你想要在稍后某个时间点(但仍然在 UI 线程上)执行异步操作。对于从后台线程直接回到 UI 线程的情况,使用 Dispatcher.Invoke
或 Application.Current.Dispatcher.Invoke
更直接。
然而,如果你已经有一个 Task
并且想要等待它完成后再在 UI 线程上执行某些操作,你可以这样做:
// 假设这是你的后台任务
Task backgroundTask = Task.Run(async () =>
{
// 模拟一些异步工作
await Task.Delay(1000); // 等待一秒作为示例
// 注意:这里我们不在后台任务中直接打开窗体
// 而是将需要在 UI 线程上执行的操作封装在一个方法中
// 然后从 UI 线程调用该方法
await Application.Current.Dispatcher.InvokeAsync(() =>
{
MyWindow window = new MyWindow();
window.Show();
}, DispatcherPriority.Normal);
// 注意:上面的 InvokeAsync 是异步的,但在这个例子中我们不需要等待它完成
// 因为我们没有其他需要在 InvokeAsync 完成之后立即执行的代码
// 如果你确实需要等待,你可以使用 InvokeAsync 的返回值(它是一个 Task)
});
// 注意:你通常不需要(也不应该)等待 backgroundTask 完成
// 除非你的应用程序逻辑确实需要等待这个后台任务完成才能继续
// 在这个例子中,我们不需要等待它,因为窗体已经在后台任务中异步显示了
但请注意,InvokeAsync
返回的是 Task
,它表示 UI 线程上的操作何时开始执行,但并不等待该操作完成。如果你需要等待窗体完全显示后再执行其他操作(尽管这通常不是必要的),你可能需要采用不同的策略(比如使用事件或命令)。