WPF 非UI线程更新UI界面的各种方法小结
转载:https://www.cnblogs.com/bdbw2012/articles/3777594.html
我们知道只有UI线程才能更新UI界面,其他线程访问UI控件被认为是非法的。但是我们在进行异步操作时,经常需要将异步执行的进度报告给用户,让用户知道任务的进度,不至于让用户误认为程序“死掉了”,特别是对于Winform,WPF等客户端程序尤为重要。
那么我们要探讨的就是如何让非UI的任务线程更新UI界面。下面对已知的几种实现方式做个总结。随着.Net版本的不断升级,实现方式还可能会增加。
1)使用Control.Invoke或Control.BeginInvoke。
.Net1.1时允许非UI的线程访问UI控件,.Net2.0开始不允许了。所以程序员首先要检测Control的InvokeRequired属性,如果为true,就说明是非UI线程访问了这个控件,于是就需要调用这两个方法之一,将操作UI的函数封装到UI线程上去执行。其中Invoke是阻塞的,BeginInvoke是异步的。


private delegate void ProgressChangedHander(int percentage);
private void UpdateUI(int percentage)
{
if (this.progressBar1.InvokeRequired)
{
//非UI线程,再次封送该方法到UI线程
this.progressBar1.BeginInvoke(new ProgressChangedHander(UpdateUI), new object[] { percentage });
}
else
{
//UI线程,进度更新
this.progressBar1.Value = percentage;
}
}


2)利用同步上下文调度器
.Net4.0增加了一个线程操作的类Task。Task的Start方法或ContinueWith方法中可以指定一个任务调度器TaskScheduler,如果这个任务调度器是同步上下文调度器,那么在Task的方法中就可以访问UI控件。要得到一个同步上下文调度器,需要通过TaskScheduler的静态方法FromCurrentSynchronizationContext。


//得到一个同步上下文调度器
TaskScheduler syncSch = TaskScheduler.FromCurrentSynchronizationContext(); Task<int> t = new Task<int>(() => Sum(100)); //在Task的ContinueWith方法中,指定这个同步上下文调度器,我们更新了form的Text属性
//去掉这个syncSch,你就会发现要出异常
t.ContinueWith(task => Text = task.Result.ToString(), syncSch);
t.Start();


PS: 其实TaskScheduler内部是使用SynchronizationContext实现的。
3)利用同步上下文SynchronizationContext
这个类很重要,利用这个类可以大大简化我们的异步更新UI界面的代码。避免了和线程间的无尽纠缠。利用SynchronizationContext的Current可以得到当前线程的同步上下文。注意,如果你在非UI线程上调用,会得到null。所以我们需要在UI线程上首先得到它的一个引用。然后在任务线程里就可以用这个引用变量。利用它的Send或Post方法将我们的更新UI的函数封送到UI线程上执行。对于WinForm程序来说Current返回的是WindowsFormsSynchronizationContext,它是SynchronizationContext的一个子类。Send或Post方法内部其实还是使用的Control.Invoke或Control.BeginInvoke来实现的。看一下它的Send方法:


public override void Send(SendOrPostCallback d, object state)
{
Thread destinationThread = this.DestinationThread;
if (destinationThread == null || !destinationThread.IsAlive) throw new InvalidAsynchronousStateException(SR.GetString("ThreadNoLongerValid"));
//这里就是用的control的invoke方法
if (this.controlToSendTo != null) this.controlToSendTo.Invoke(d, new object[] { state });
}


注意:Send方法是阻塞的,Post方法是异步的。
喜欢刨根问底的,比如我,又在想,Control的Invoke是如何实现线程间的封送的呢?我们来略微调查一下。


public object Invoke(Delegate method, params object[] args)
{
using (new MultithreadSafeCallScope())
{
return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true);
}
}


Invokie里调用了MarshaledInvoke方法。一看Marshal就知道有封送的意思。为了不偏离主题,对MarshaledInvoke这个方法的代码保留主要的部分,有个印象就行,大家不用太较真,毕竟是Mircrosoft内部的代码,没太多的闲工夫来研究。


private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
int num;
//…
bool flag = false;
//判断是不是UI线程调用的Invoke
if (SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, this.Handle), out num) == SafeNativeMethods.GetCurrentThreadId() && synchronous) flag = true;
ExecutionContext executionContext = null;
//如果不是,获得UI线程的执行上下文
if (!flag) executionContext = ExecutionContext.Capture();
//利用这个UI线程的上下文,构造一个线程调用方法入口
ThreadMethodEntry entry = new ThreadMethodEntry(caller, this, method, args, synchronous, executionContext);
lock (this)
{
if (this.threadCallbackList == null) this.threadCallbackList = new Queue();
}
lock (this.threadCallbackList)
{
if (threadCallbackMessage == 0) threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");//注册一个消息
this.threadCallbackList.Enqueue(entry);//将调用方法加入线程调用队列
}
if (flag)
this.InvokeMarshaledCallbacks();//同步,马上执行
else
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);//异步:发送消息,UI得到消息就会调用
if (!synchronous) return entry;
if (!entry.IsCompleted) this.WaitForWaitHandle(entry.AsyncWaitHandle);
if (entry.exception != null) throw entry.exception;
return entry.retVal;
}


上面的方法的内部实现较为复杂,勉强注释了几个地方,一家之言,不可全信。大意可能大家都明白了,对于BeginInvoke异步调用,它用了消息泵,UI线程可以提取到这个消息,并执行相应的函数。而对于同步的Invoke忍不住又查了点:
ExecutionContext.Run(tme.executionContext, invokeMarshaledCallbackHelperDelegate, tme);
这里的tme就是ThreadMethodEntry,说明ExecutionContext的静态方法Run是不是实现了线程的切换呢?不再继续调查了,我们只用记住,Control的Invoke和BeginInvoke可以实现到UI线程的切换就行了。
说着说着就远离主题了,下面来看看SynchronizationContext的用法:


private void SyncContextTest()
{
//UI线程的ISynchronizationContext取得
SynchronizationContext syncContext = SynchronizationContext.Current; //新建一个模拟操作i
ThreadPool.QueueUserWorkItem((o) =>
{
for (int i = 0; i < 100; i++)
{
//模拟耗时
Thread.Sleep(100);
//通知用户
syncContext.Post(new SendOrPostCallback(ProgressCallBack), i);
}
}
);
}
private void ProgressCallBack(object percent)
{
//不再判定是不是UI线程
this.progressBar1.Value = (int)percent;
}


但是上面的代码还是有点缺陷,就是Post的回调函数参数只能是object的,要强行转换成int。但我们可以像下面这样修改,为用户提供一个int型的接口。


delegate void UserNotifyProcess(int percent);
private void SyncContextTest()
{
// UI线程的ISynchronizationContext取得
SynchronizationContext syncContext = SynchronizationContext.Current; UserNotifyProcess userNotify = null;
userNotify += new UserNotifyProcess(ProgressCallBack); //新建一个模拟操作i
ThreadPool.QueueUserWorkItem((o) =>
{
for (int i = 0; i < 100; i++)
{
//模拟耗时
Thread.Sleep(100);
//通知用户
syncContext.Post((param) =>
{
//这里是关键了,只要到这里就说明是UI线程了
if (userNotify != null)
{
userNotify((int)param);
}
},
i);
}
}
);
}


WPF 非UI线程更新UI界面的各种方法小结的更多相关文章
- Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面
Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍 ...
- Android异步处理系列文章四篇之一使用Thread+Handler实现非UI线程更新UI界面
目录: Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+L ...
- 学习通过Thread+Handler实现非UI线程更新UI组件
[Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则 ...
- 学习通过Thread+Handler实现非UI线程更新UI组件(转)
[Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则 ...
- Winform非UI线程更新UI界面的各种方法小结
我们知道只有UI线程才能更新UI界面,其他线程访问UI控件被认为是非法的.但是我们在进行异步操作时,经常需要将异步执行的进度报告给用户,让用户知道任务的进度,不至于让用户误认为程序“死掉了”,特别是对 ...
- 非UI线程更新UI界面的各种方法小结
转载:https://www.cnblogs.com/xiashengwang/archive/2012/08/18/2645541.html 我们知道只有UI线程才能更新UI界面,其他线程访问UI控 ...
- 一种WPF在后台线程更新UI界面的简便方法
WPF框架规定只有UI线程(主线程)可以更新界面,所有其他后台线程无法直接更新界面.幸好,WPF提供的SynchronizationContext类以及C#的Lambda表达式提供了一种方便的解决方法 ...
- android 在非UI线程更新UI仍然成功原因深入剖析
”只能在UI主线程中更新View“. 这句话很熟悉吧? 来来,哥们,看一下下面的例子 @Override protected void onCreate(Bundle savedInsta ...
- Android子线程更新UI的方法总结
版权声明:本文为博主原创文章,转载请注明出处:https://i.cnblogs.com/EditPosts.aspx?postid=6121280 消息机制,对于Android开发者来说,应该是非常 ...
随机推荐
- Java nio 理解
Java nio 称为Java new IO ,对Java io而言的.他有两个主要的概念:缓存.通道. 在程序中,数据的来源或写入,要么网络.要么硬盘.所有通道分为:文件通道.TCP通道.UDP通道 ...
- Linux配置python环境1,pyenv
安装pyenv sudo apt-get install curl git curl -L https://github.com/pyenv/pyenv-installer/raw/master/bi ...
- 【JZOJ5605】【NOI2018模拟3.26】Arg
题目描述 给出一个长度为 m 的序列 A, 请你求出有多少种 1...n 的排列, 满足 A 是它的一个 LIS. 解题思路 如何求出一个序列的LIS? 对于二分的方法,每次插入一个数,将它放到第一个 ...
- C# List分组
//分组 8个为一组 List<List<string>> ArrayList = sArray.Select((x, i) => new { Index = i, Va ...
- CentOS7 安装MySQL8修改密码
1. 添加MySQL8的本地源 执行以下命令获取安装MySQL源 [root@virde ~]# wget https://repo.mysql.com//mysql80-community-rele ...
- axios 的坑
必须安装axios 和qs 1.main.js中的配置 import axios from 'axios' import qs from 'qs'; axios.defaults.headers.po ...
- HTTP中GET与POST的区别颠覆式的理解
关于http中的get和post你轻轻松松的给出了一个“标准答案”: GET在浏览器回退时是无害的,而POST会再次提交请求. GET产生的URL地址可以被Bookmark,而POST不可以. GET ...
- 尚学堂requireJs课程---2、模块
尚学堂requireJs课程---2.模块 一.总结 一句话总结: # 将代码以及使用放到独立的闭包中去,并且赋值给了变量,便于外部访问 # return出了函数和变量(放在一个json对象中) # ...
- drawable SVG 使用
Android会使用一些规则来去帮我们匹配最适合的图片.什么叫最适合的图片?比如我的手机屏幕密度是xxhdpi,那么drawable-xxhdpi文件夹下的图片就是最适合的图片.因此,当我引用andr ...
- Python关于%matplotlib inline
Python关于%matplotlib inline %matplotlib inline 是一个魔法函数(Magic Functions).官方给出的定义是:IPython有一组预先定义好的所谓的魔 ...