【WPF】在新线程上打开窗口
当WPF应用程序运行时,默认会创建一个UI主线程(因为至少需要一个),并在该UI线程上启动消息循环。直到消息循环结束,应用程序就随即退出。那么,问题就来了,能不能创建新线程,然后在新线程上打开一个新窗口实例?这样可以让不同窗口运行在不同的线程上,一定程度上可以相互“独立”。
其实呢,完全的独立运转似乎不太可能,毕竟嘛,线程是抢占 CPU 时间片的,即各个线程间是交替运行的,现在处理器基本是N核的,可以结合并发一起用(在.net 中,使用 Task 可以自动并发)。不管怎么说吧,对UI的响应能力应该能有所改善的。
有大伙伴一定会说,这TMD Easy了,来直接上一段 Code。
Task theTask = new Task(() =>
{
SecondWindow wind = new SecondWindow();
wind.Show();
});
theTask.Start();
然后你满怀信心,春光满面地按下了【F5】键,结果……
是了,不知道大伙伴以前在创建 WinForms 项目时,有没有注意 Main 方法上面的一个 Attribute 的应用。
[System.STAThreadAttribute()]
public static void Main(string[] args) { }
不仅仅是COM组件,Windows的 UI 调用,也需要 STA 线程单元。可是 Task 类没有公开相关的成员让我们设置,只有 Thread类有一个 SetApartmentState 方法,可以用 ApartmentState 枚举来进行线程单元设置。
其实,你可以直接用 Thread 类,比如这样。
Thread t = new Thread(() =>
{
SecondWindow win = new SecondWindow();
win.Show();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
如果,你还想结合 Task 类一起用,可以封装成一个方法。
private Task RunNewWindowAsync<TWindow>() where TWindow:System.Windows.Window, new()
{
TaskCompletionSource<object> tc = new TaskCompletionSource<object>();
// 新线程
Thread t = new Thread(() =>
{
TWindow win = new TWindow();
win.Show();
// 这句话是必须的,设置Task的运算结果
// 但由于此处不需要结果,故用null
tc.SetResult(null);
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
// 新线程启动后,将Task实例返回
// 以便支持 await 操作符
return tc.Task;
}
TaskCompletionSource 类可以从其他来源获得代码执行结果,然后生成一个带Result 的Task实例,有了这个Task实例就可以使用异步等待语法了(await操作符)。由于我们这个地方只是Show一个窗口就完事了,不需要产生执行结果,但是,TaskCompletionSource类有一个泛型参数,用以指定执行结果。
这里咱们可以这样,实例化TaskCompletionSource时设定泛型参数类型为object,然后把代码的执行结果设置为 null。要设置执行结果,请调用 SetResult 方法,要得到生成的Task实例,请访问 Task 属性。
故,上面代码可以这样改:
private Task RunNewWindowAsync<TWindow>() where TWindow:System.Windows.Window, new()
{
TaskCompletionSource<object> tc = new TaskCompletionSource<object>();
// 新线程
Thread t = new Thread(() =>
{
TWindow win = new TWindow();
win.Show();
// 这句话是必须的,设置Task的运算结果
// 但由于此处不需要结果,故用null
tc.SetResult(null);
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
// 新线程启动后,将Task实例返回
// 以便支持 await 操作符
return tc.Task;
}
还要注意,一定要调用Thread实例的 SetApartmentState 方法把线程单元设置为STA,一定要在线程 Start 之前设置,Start 之后就不能改了。最后把生成的Task实例从方法返回。
好了,到了这一步,窗口可以在新线程上打开了,但是,你又会发现一个问题——窗口打开后,闪一下就关闭了。那是因为我们没有在新线程上开启消息循环。大伙伴皆知,WPF 中有一个类专门调度UI线程,对,就是那个家伙:Dispatcher。Dispatcher 类公开了一个静态方法,叫Run,只要在相应的线程上调用该方法,新的消息循环就会开启。
来,咱们改一个代码。
Thread t = new Thread(() =>
{
TWindow win = new TWindow();
win.Show();
// Run 方法必须调用,否则窗口一打开就会关闭
// 因为没有启动消息循环
System.Windows.Threading.Dispatcher.Run();
// 这句话是必须的,设置Task的运算结果
// 但由于此处不需要结果,故用null
tc.SetResult(null);
});
在主窗口的代码中,如此调用上面的 RunNewWindowAsync 方法。
Button b = e.Source as Button;
b.IsEnabled = false;
await RunNewWindowAsync<SecondWindow>(); //可异步等待
b.IsEnabled = true;
在等待之前,我禁用了按钮,只是为了不让同一个窗口打开多个实例而已,等新窗口结束后,按钮就会重新启用。
现在这个示例已基本接近我们的预期。但是,你运行后又会发现新问题——新窗口被关闭后,主窗口上的按钮依然不可用,那是因为新线程上的消息循环仍在继续,咋办呢?很简单,窗口不是有个 Closed 事件吗,我们加一个 handler ,当窗口关闭后马上把线程上的消息循环结束,这样Task就能马上返回。
TWindow win = new TWindow();
win.Closed += (d, k) =>
{
// 当窗口关闭后马上结束消息循环
System.Windows.Threading.Dispatcher.ExitAllFrames();
};
win.Show();
老周在不久前的一篇烂文中介绍过 DispatcherFrame 这个东东,还记得吧,前面咱们说过,向调度队列中插入一个 frame 就会开启一个消息循环,所以,调用 Dispatcher 的 ExitAllFrames 方法,可以马上结束当前线程上的所有 frame,就相当于跳出所有消息循环。这样处理后,当新打开的窗口被关闭后,Task任务马上完成,按钮就可以及时恢复可用。
好了,好了,到这一步,咱们的预期效果就达到了。看看结果吧。
===================================================================
说一句题外话,最近老周的博客更新得较慢,特特说明一下,不是老周偷懒,而是因为暑假到了,老周的书法培训班又要开工了。正忙于误人子弟呢,所以博客更新频率会慢一些。
【WPF】在新线程上打开窗口的更多相关文章
- WPF 使用Caliburn.Micro 多线程打开窗口
我们都知道在WPF里面用多线程打开一个窗口很简单.如下 public void ClickMe(object sender) { Thread newWindowThread = new Thread ...
- CefSharp禁止弹出新窗体,在同一窗口打开链接,或者在新Tab页打开链接,并且支持带type="POST" target="_blank"的链接
说明:在同一窗口打开链接,只要稍加改造就可以实现,这里实现的是在新Tab页打开链接,并且支持带type="POST" target="_blank"的链接 gi ...
- 在C#中子线程如何操作主窗口线程上的控件
在C#中子线程怎样操作主线程中窗口上控件 在C#中,直接在子线程中对窗口上的控件操作是会出现异常,这是因为子线程和运行窗口的线程是不同的空间,因此想要在子线程来操作窗口上的控件.是不可能简单的通过控件 ...
- Window.open 实现导航与打开窗口,导航到一个特定链接地址,也可以打开一个新的浏览器窗体
语法 window.open(strUrl,strWindowName,strWindowFeatures ,replace) strUrl: 打开资源的地址 strWindowName: 表示窗体名 ...
- 运行vs时打开一个浏览器窗口,而不是在原有窗口上打开一个标签
1.运行vs时打开一个浏览器窗口,而不是在原有窗口上打开一个标签,结束调试时窗口又关闭了,特别麻烦. 在用swagger调试接口时,好不容易输入了测试数据,然而窗口关闭了,再次调试又得重新输入. 解决 ...
- window.open新打开窗口与新开标签页
最近在使用window.open时忽略了一个细节问题:window.open新打开一个窗口,但是有时却是新打开一个窗口有时打开一个新标签页.虽然对一般的需求来说,这个两种情况都无所谓,但是对于那种有强 ...
- [WPF]使用CheckAccess检测是否在控件的ui线程上执行
private void Parallel(object sender, RoutedEventArgs e) { Task.Run(() => ChangeColour(Brushes.Red ...
- 在 WPF 中的线程
线程处理使程序能够执行并发处理,以便它可以做多个操作一次.节省开发人员从线程处理困难的方式,设计了 WPF (窗口演示文稿基金会).这篇文章可以帮助理解线程在 WPF 中的正确用法. WPF 内部线程 ...
- C#线程安全打开/保存文件对话框
在多线程单元模式(MTA)中为应用程序使用.NET OpenFileDialog和SaveFileDialog 下载FileDialogsThreadAppartmentSafe_v1.zip 如果您 ...
随机推荐
- 关于Dubbo一个接口多个实现的解决方案
如题,其实这个问题在官方文档中已经说明了.我直接贴图就好了 更多学习请参考:minglisoft.cn/technology
- Visual Studio2017中如何让Entity Framework工具【ADO.NET实体数据模型】支持MYSQL数据源
熟悉Entity Framework应该对以下图片不陌生,他就是ADO.NET实体数据模型向导:可以将数据库的表自动生成模型类,或者创建Code First的模型文件. 但是这个模型向导默认只显示微软 ...
- pod install 出现 Unable to find a specification for `xxxxx` 解决方案
pod repo update 更新一下repo,更新完成之后即可解决无法找到xxx第三方框架的问题了
- UITextField关闭自动联想功能
在textField输入内容时,如果内容为英文,输入的英文如果不正确的单词就是有红色的线报警,关闭英文自动联想功能 self.autocorrectionType = UITextAutocorrec ...
- 深入解析java String中getBytes()的编码问题
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6900536.html Java服务器后台在和Android端App通信时,遇到了两端关于用MD5加密同一包含 ...
- 【Netty】EventLoop和线程模型
一.前言 在学习了ChannelHandler和ChannelPipeline的有关细节后,接着学习Netty的EventLoop和线程模型. 二.EventLoop和线程模型 2.1. 线程模型 线 ...
- SmartCoder每日站立会议09
站立会议内容 今天约了在一起编程,详细确定各个页面以及消息的添加.发送等一些小的细节. 1.站立会议照片: 2.任务展板 2.燃尽图
- Flume简介及安装
Hadoop业务的大致开发流程以及Flume在业务中的地位: 从Hadoop的业务开发流程图中可以看出,在大数据的业务处理过程中,对于数据的采集是十分重要的一步,也是不可避免的一步,从而引出我们本文的 ...
- itmacy_我的博客
开通博客的第一天,并不希望自己以后像写流水账一样来写自己的博客,而是希望每一篇博客,无论是转载还是原创,都是经过深思熟虑,并且有意义的...
- java中的流程控制语句总结
程序的结构分类: 顺序结构:按照写代码的顺序 一次执行 选择结构:根据条件的不同有选择的执行不同的代码 循环结构:在一定条件下 反复执行某一片代码 选择结构: 也叫分支结构 根据条件的不同,有选择的执 ...