5.1 案例题目(概括异常,如:WPF 及winfrom界面假死)
5.1.1 异常描述
占用主UI线程,运行耗时程序代码导致界面假死无法点击及操作。
5.1.2 原因分析
(是什么原因造成的异常,或可能由哪些原因导致异常,适当作图以便理解)
只有创建DispatcherObject对象的线程才能访问之,其他线程不可访问该对象,比如UI线程创建的对象其他后台线程就不可访问。但是如果直接在主UI线程添加耗时代码。就会导致界面假死。
5.1.3 解决思路
(针对原因,如何解决或改善)
那么如果想访问该UI线程创建的控件,就必须将一个访问该控件的方法委托给创建控件的UI线程的Dispatcher去执行,才能假借UI线程之手去访问控件。this.Dispatcher.Invoke就是使用UI线程的Dispatcher调用Invoke方法执行该委托,但是这个委托中使用了比较耗时的网络请求,也就是说该请求如果在Invoke方法中,就会占用UI线程的时间片去执行,即使放在了创建的线程中。
本质上this.Dispatcher.Invoke或者BeginInvoke都是主UI线程执行了委托里面的代码。
可参考下面代码示例理解。
5.1.4 对策制定
(1)
初始化线程 |
方法1
入参,方法 |
(具体方法及修改前后图)
修改前:
Thread thread = new Thread(new ThreadStart(delegate {
this.Dispatcher.Invoke(new Action(() => {
将action作为一个委托传给invoke方法 |
// 获取控件数据并且构造参数param
耗时操作 |
...
主UI线程this |
// 请求网络
responseText = WebServiceHandler.ObjectWebServer.getObject(param);
// 解析响应数据
Object o = JsonTools<Object>.DeserializeList(responseText);
控件操作—略 |
// 更新控件
...
}));
}))
thread.Start();
如上图,
在invoke里面执行了耗时操作。该耗时操作依然占用主UI线程。不过是不是异步调用的Invoke。他在耗时操作时都会导致界面假死。具体代码段解析可参考上图及解决思路。
修改后:
Thread thread = new Thread(new ThreadStart(delegate {
// 获取控件数据并且构造参数param
耗时操作 |
...
// 请求网络
responseText = WebServiceHandler.ObjectWebServer.getObject(param);
// 解析响应数据
Object o = JsonTools<Object>.DeserializeList(responseText);
this.Dispatcher.Invoke(new Action(() => {
控件操作—略 |
// 更新控件
...
}));
}))
thread.Start();
解决方法就是不让耗时操作占UI主线程。具体操作就是将耗时的代码段放到invok外面,新建的线程里面。
没办法截图,凑合着看吧。
(2) 方法2
(3) new Thread(() =>
(4)
(5) {
(6)
(7) Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(8)
(9) new Action(() =>
(10)
(11) {
(12)
(13)
(14) // Dispatche 异步方法!!" + DateTime.Now.ToString();
(15)
(16) }));
(17)
(18) }).Start();
各种方法大同小异。基本思路:
1、靠另起一个线程或者异步方法跳过耗时代码这段。
2、在另起的这段代码里用Dispatcher.BeginInvoke或者Invoke委托来调用UI线程的控件,进而执行控件的更新。
3、切记不要将耗时的代码写在InVoke里面就成。
(3)方法3
这个方法比较复杂:除非有要持续监控并显示在主界面的内容。
思路
1、外围再起一个线程。以目前标准scada AlarmHandler为例。当然Timer定时器也可以,方便开启和关闭当前监听的循环(以90340CNC设备监控为例)。
2、在合适的位置实例化并开启该线程或者循环的监听。
3、该线程部分代码范例可参考AlarmHandler相关部分。原来这个部分有当前部分报警(有前端显示变更)变更,是靠委托和事件触发前端列表变更的。后面改成的public的全局变量。然后由前端UI线程开定时器去刷这个值(占主线程)。
对于这个部分为什么不用委托和事件,而要改成占主线程的定时器去刷。是因为委托和事件的用法有问题。
我是这么理解的。在timer定时器监控循环里面,在有监控的变量刷新(变化)的时候,触发对应委托事件,前端页面做相应的变更。这个需要在页面加载的时候订阅这个事件(当然也要靠Dispatcher.Invoke来对控件修改)。
CNCEvent.sentenceChangeAfter_DelegateHandler += new CNCEvent.SentenceChangeAfter_delegateHandler(SetSentence);
这里我的监控画面并不是主窗体MianView。所以在界面开启关闭再开启时就会报该委托必须有一个目标(且仅有一个目标的错误。委托和事件就无法执行。根源应该是订阅的两个相同的事件。不对,就不是事件。
标签:控件,winfrom,假死,耗时,线程,new,UI,WPF,Dispatcher From: https://www.cnblogs.com/sanzhu/p/14888357.html