六种多线程方法解决UI线程堵塞
http://blog.csdn.net/oyi319/article/details/6851371
一、六种多线程方法
.NET Framework2.0框架提供了至少4种方式实现多线程,它们是“BackgroundWorker”组件、委托的异步调用、线程池ThreadPool以及线程类Thread;.NET Framework 4.0增加了任务并行库TPL和PLINQ技术,可利用Task和并行计算的方法实现。下面列举这6种方法。
1. BackgroundWorker组件
命名空间:System.ComponentModel
程序集:System.dll
BackgroundWorker可以用于协助开发WinForm应用程序或WPF应用程序。它作为一个组件发布,提供工作线程的进度反馈、完成事件和取消工作线程方法。在Visual Studio设计器界面里,通过“工具箱”方便地将它加入设计界面,还能通过“属性”窗口设置它的属性和事件。
将工作代码写在DoWork事件处理程序里,在主线程(UI线程)调用BackgroundWorker对象的RunWorkerAsync方法即可在一个独立的线程里启动DoWork事件的处理程序。
- backgroundWorker1.RunWorkerAsync(new Parameters(PrimesFrom,PrimesTo));
若需要在UI上显示工作进度,先使BackgroundWorker对象的WorkerReportsProgress属性设置为True,然后在DoWork事件处理程序里直接用BackgroundWorker对象的ReportProgress方法向UI线程报告工作进度和进度信息,编写BackgroundWorker对象的ProgressChanged事件处理程序,来获得工作进度,更新UI上的进度条等。
通常我们还希望点击“取消”按钮,能取消后台工作任务。先使BackgroundWorker对象的WorkerSupportsCancellation属性设置为True,然后在“取消”按钮的单击事件处理程序里直接调用BackgroundWorker对象的CancelAsync方法;在DoWork事件处理程序里,通过BackgroundWorker对象的CancellationPending属性便可得知是否有请求取消操作。
- backgroundWorker1.CancelAsync();
当DoWork事件处理程序返回后,会在UI线程上产生RunWorkerCompleted事件。
2. 委托的异步调用
.NET Framework中的许多对象支持同步和异步两种调用方法,它们的异步调用方法名称如BeginXXX。委托也支持同步调用(Invoke)和异步调用(BeginInvoke)两种方式。异步调用是不阻塞当前线程,使委托的方法与调用方代码异步执行;也可以在后台线程里,通过调用支持异步方法的.NET Framework对象(如WinForm的Form对象和WPF的Dispatcher对象)委托的代码,使代码在这些对象所在的线程里执行——这个技巧在后台线程请求执行UI线程上的代码时非常有用。
- if (this.InvokeRequired) //Form1的多线程方法中的代码片段(WPF中也有类似的属性)
- {
- varupdate = new Action(TaskCompleted); //调用TaskCompleted方法更新UI
- this.BeginInvoke(update);
- }
在当前的类或者一个新类里编写一个后台执行代码的入口方法,然后在UI线程里声明指向此入口方法的委托对象,执行委托对象的BeginInvoke方法即可。委托对象的BeginInvoke方法的参数由两部分组成,第一部分是委托函数的参数,第二部分是委托方法异步调用完成后启动的方法和参数,可以不指定第二部分。
- varworker = new Action<Parameters>(FindPrimesViaDelegate); //委托
- worker.BeginInvoke(new Parameters(PrimesFrom,PrimesTo), TaskComplete, null);
从.NET Framework 3.5开始,支持9个传入参数的Action泛型委托和8个传入参数、1个返回值的Func泛型委托,到.NETFramework 4.0,支持传入参数达16个的Action和Func泛型委托。它们被定义在System命名空间,程序集mscorlib.dll,从.NET Framework 3.5时代后,较少的使用Delegate关键字自定义委托了。
3. 线程池ThreadPool
命名空间:System.Threading
程序集:mscorlib.dll
每个进程拥有一个线程池。托管代码的线程池的最多支持线程数目与.NET Framework版本及CPU数目等硬件环境有关。在.NET Framework 4.0中,默认每个可用的CPU处理器增加250个辅助线程和1000个I/O线程。可使用SetMaxThreads方法更改线程池的最多线程数(注:承载.NET Framework的非托管代码,如C++,可使用mscoree.h头文件的CorSetMaxThreads函数更改线程池大小)。除了SetMaxThreads方法,还可以使用GetMaxThreads、GetMinThreads、SetMinThreads方法获得或更改线程数。.NET
Framework中的许多多线程的类或组件(如System.Threading.Timer),就是在线程池中运行的。
需要记住一点的是,线程池线程都是后台线程,即线程池线程的IsBackground属性都为True,全部前台线程退出后,线程池线程将被强行中断。
用QueueUserWorkItem方法将一个无参数或者仅一个参数的void方法加入到线程池启动。
- ThreadPool.QueueUserWorkItem(FindPrimes, newParameters(PrimesFrom, PrimesTo)); //启动一个线程池线程
4. 线程Thread
命名空间:System.Threading
程序集:mscorlib.dll
将一个无参数或者仅一个参数的void方法委托给Thread实例,调用Thread对象的Start方法启动一个线程,可对它进行优先级、前后台线程、线程单元状态、线程状态及名称等更多细致的控制。
- var t= new Thread(FindPrimes)
- {
- Name = "FindPrimes",
- IsBackground = true
- };
- t.Start(new Parameters(PrimesFrom, PrimesTo)); //启动一个线程
5. 任务Task
命名空间:System.Threading.Tasks
程序集:mscorlib.dll
Task作为.NETFramework 4.0推崇的多线程代替办法,方便的控制任务的有序或并行执行,充分地发挥多核CPU性能,将多项任务平衡分配给每个可用的CPU。由于任务中的某些方法使用了数据共享锁技术,可使用Dispose方法显式地销毁这些资源。泛型版本的任务还能取得其返回结果,通常任务作为数组并发执行的。利用Windows任务管理器或性能监视器能监视程序的CPU利用率的波形图。
- _tokenSource= new CancellationTokenSource();//用于取消任务
- Task.Factory.StartNew(FindPrimesInTask,new Parameters(PrimesFrom,PrimesTo), _tokenSource.Token); //启动一个任务
6. 并行计算Parallel
命名空间:System.Threading.Tasks
程序集:mscorlib.dll
充分发挥CPU的多核性能,Parallel.Invoke方法可以同时运行多个并行任务,Parallel.For和Parallel.ForEach方法可以并行循环和迭代IEnumerable<T>集合。
- Parallel.For(parameters.Min, parameters.Max,
- (count, loop) =>
- {
- //并行执行的代码
- //注:在Parallel.For里,第一个参数是min至max的自变量,
- //第二个参数是指这个并行循环体参数,可执行中断并行循环体等控制方法
- }
二、取消线程
除了BackgroundWorker组件,在.NETFramework 4.0之前的多线程框架中是不提供类似CancellationToken类型用于支持多线程的取消请求的。常用的办法是轮询检查一个线程间共享的取消标记的方式,获得取消请求,甚至编写一个线程管理器来增强多线程的可控性。
.NET Framework4.0对多线程和并行运算进行了增强和改进,CancellationTokenSource对象可以在多线程方案中更有效的发出取消请求。
命名空间:System.Threading
程序集:mscorlib.dll
- //1.声明一个CancellationTokenSource对象
- private CancellationTokenSource _tokenSource;
- //2.实例化_tokenSource对象
- _tokenSource= new CancellationTokenSource();
- //3.在支持CancellationToken的代码里判断取消请求
- if(_tokenSource.IsCancellationRequested)
- {
- loop.Stop();
- }
- //4.在控制线程代码里用调用取消请求
- _tokenSource.Cancel();
三、演示
在Wrox出版社的《VisualBasic 2005高级编程》第22章有一个求10000内素数的例子讲解.NET的多线程技术,我写了类似的演示代码,演示6种多线程的方法在后台计算素数,而不阻塞UI。

值得一提的是,在任务并发计算素数的演示里,利用CPU多核计算,得到结果所花费的时间有显著的提高。上图中最后一个波峰可看到CPU 0和CPU 1的并行工作情况。
代码和演示程序,请下载附件。
多线程演示_Src.7z 20KB:http://m2nlight.ys168.com/
六种多线程方法解决UI线程堵塞的更多相关文章
- C# 多线程详解 Part.02(UI 线程和子线程的互动、ProgressBar 的异步调用)
我们先来看一段运行时会抛出 InvalidOperationException 异常的代码段: private void btnThreadA_Click(object sender, ...
- c#多线程(UI线程,控件显示更新) Invoke和BeginInvoke 区别
如果只是直接使用子线程访问UI控件,直接看内容三,如果想深入了解从内容一看起. 一.Control.Invoke和BeginInvoke方法的区别 先上总结: Control.Invoke 方法 (D ...
- [转] c#多线程(UI线程,控件显示更新) Invoke和BeginInvoke 区别
如果只是直接使用子线程访问UI控件,直接看内容三,如果想深入了解从内容一看起. 一.Control.Invoke和BeginInvoke方法的区别 先上总结: Control.Invoke 方法 (D ...
- Winform非UI线程更新UI界面的各种方法小结
我们知道只有UI线程才能更新UI界面,其他线程访问UI控件被认为是非法的.但是我们在进行异步操作时,经常需要将异步执行的进度报告给用户,让用户知道任务的进度,不至于让用户误认为程序“死掉了”,特别是对 ...
- 非UI线程更新UI界面的各种方法小结
转载:https://www.cnblogs.com/xiashengwang/archive/2012/08/18/2645541.html 我们知道只有UI线程才能更新UI界面,其他线程访问UI控 ...
- WPF 非UI线程更新UI界面的各种方法小结
转载:https://www.cnblogs.com/bdbw2012/articles/3777594.html 我们知道只有UI线程才能更新UI界面,其他线程访问UI控件被认为是非法的.但是我们在 ...
- C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较
使用Task,await,async,异步执行事件(event),不阻塞UI线程和不跨线程执行UI更新 使用Task,await,async 的异步模式 去执行事件(event) 解决不阻塞UI线程和 ...
- 学习通过Thread+Handler实现非UI线程更新UI组件
[Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则 ...
- 学习通过Thread+Handler实现非UI线程更新UI组件(转)
[Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则 ...
随机推荐
- 0基础学小程序----day2
目录结构的三部分如上 下来我们看看 框架全局文件 app.js小程序逻辑 再看app.json 小程序公共设置可以对五个功能进行设置 我们再来逐一看看 页面路径那里 你建个路径,他会自动生成文件夹 ...
- BaiduSpider:爬取百度的利器
视频链接:https://www.zhihu.com/zvideo/1272864710321516544 BaiduSpider是一个能够爬取百度搜索结果的Python爬虫,轻量但强大.目前支持百度 ...
- Redis 实战篇:巧用Bitmap 实现亿级海量数据统计
在移动应用的业务场景中,我们需要保存这样的信息:一个 key 关联了一个数据集合. 常见的场景如下: 给一个 userId ,判断用户登陆状态: 显示用户某个月的签到次数和首次签到时间: 两亿用户最近 ...
- Mysql5.6.47开放远程访问(修改远程访问密码)
1.登录mysql mysql -u root -p 然后输入密码,登录进去mysql: 2.切换数据库 use mysql; 3.修改权限 GRANT ALL PRIVILEGES ON *.* T ...
- VMware 启动虚拟机黑屏(Ubuntu)
之前启动都很正常,不知道为啥,最近启动,老出现这种黑屏得情况: 解决方法: 在搜索栏里搜索cmd 巴拉了一下谷歌,发现了问题: 虚拟机和主机之间的通信,基本上是以 socket 的方式进行通信的(这里 ...
- C#的生产者和消费者 实例
class Program { //写线程将数据写入myData static int myData = 100; //读写次数 const int readWriteCount = 20; //fa ...
- C++ 矩形交集和并集的面积-离散化
//离散化,x,y坐标分别按从小到大排序 //离散化 //1.首先分离出所有的横坐标和纵坐标分别按升序存入数组X[ ]和Y[ ]中. //2. 设数组XY[ ][ ].对于每个矩形(x1,y1)(x2 ...
- Java 数组结构
数组是最常见的一种数据结构,是相同类型的.用一个标识符封装到一起的基本类型数据序列或对象序列.可以用一个统一的数组名和下标来唯一确定数组中的元素.实质上数组是一个简单的线性序列,因此数组访问起来很快. ...
- 不同的 count 用法
不同的 count 用法效率:在 select count(?) from t 这样的查询语句里面, count(*).count(主键 id).count(字段) 和 count(1) 等不同用法的 ...
- BeanUtils使用:从一个map集合中,拷贝到javaBean中(四)
package beanutil; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; impo ...