转:多线程六种多线程方法解决UI线程阻塞

摘要:
在主线程(UI线程)上调用BackgroundWorker对象的RunWorkerSync方法,以在独立线程中启动DoWork事件处理程序。然后,使用DoWork事件处理程序中BackgroundWorker对象的ReportProgress方法将工作进度和进度信息直接报告给UI线程,并直接调用按钮单击事件处理程序的BackgroundWorkers对象的CancelAsync方法;
 

一、六种多线程方法

.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事件的处理程序。

  1. 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属性便可得知是否有请求取消操作。

  1. backgroundWorker1.CancelAsync(); 


当DoWork事件处理程序返回后,会在UI线程上产生RunWorkerCompleted事件。

2.      委托的异步调用

.NET Framework中的许多对象支持同步和异步两种调用方法,它们的异步调用方法名称如BeginXXX。委托也支持同步调用(Invoke)和异步调用(BeginInvoke)两种方式。异步调用是不阻塞当前线程,使委托的方法与调用方代码异步执行;也可以在后台线程里,通过调用支持异步方法的.NET Framework对象(如WinForm的Form对象和WPF的Dispatcher对象)委托的代码,使代码在这些对象所在的线程里执行——这个技巧在后台线程请求执行UI线程上的代码时非常有用。

  1. if (this.InvokeRequired) //Form1的多线程方法中的代码片段(WPF中也有类似的属性) 
  2. varupdate = new Action(TaskCompleted); //调用TaskCompleted方法更新UI 
  3. this.BeginInvoke(update); 

在当前的类或者一个新类里编写一个后台执行代码的入口方法,然后在UI线程里声明指向此入口方法的委托对象,执行委托对象的BeginInvoke方法即可。委托对象的BeginInvoke方法的参数由两部分组成,第一部分是委托函数的参数,第二部分是委托方法异步调用完成后启动的方法和参数,可以不指定第二部分。

  1. varworker = new Action<Parameters>(FindPrimesViaDelegate); //委托 
  2. 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方法加入到线程池启动。

  1. ThreadPool.QueueUserWorkItem(FindPrimes, newParameters(PrimesFrom, PrimesTo)); //启动一个线程池线程 

4.      线程Thread

命名空间:System.Threading

程序集:mscorlib.dll

将一个无参数或者仅一个参数的void方法委托给Thread实例,调用Thread对象的Start方法启动一个线程,可对它进行优先级、前后台线程、线程单元状态、线程状态及名称等更多细致的控制。

  1. var t= new Thread(FindPrimes) 
  2.          { 
  3.              Name = "FindPrimes"
  4.              IsBackground = true 
  5.          }; 
  6. t.Start(new Parameters(PrimesFrom, PrimesTo)); //启动一个线程 

5.      任务Task

命名空间:System.Threading.Tasks

程序集:mscorlib.dll

Task作为.NETFramework 4.0推崇的多线程代替办法,方便的控制任务的有序或并行执行,充分地发挥多核CPU性能,将多项任务平衡分配给每个可用的CPU。由于任务中的某些方法使用了数据共享锁技术,可使用Dispose方法显式地销毁这些资源。泛型版本的任务还能取得其返回结果,通常任务作为数组并发执行的。利用Windows任务管理器或性能监视器能监视程序的CPU利用率的波形图。

  1. _tokenSource= new CancellationTokenSource();//用于取消任务 
  2. 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>集合。

  1. Parallel.For(parameters.Min, parameters.Max, 
  2.     (count, loop) => 
  3.     { 
  4. //并行执行的代码 
  5. //注:在Parallel.For里,第一个参数是min至max的自变量, 
  6. //第二个参数是指这个并行循环体参数,可执行中断并行循环体等控制方法 

二、取消线程

除了BackgroundWorker组件,在.NETFramework 4.0之前的多线程框架中是不提供类似CancellationToken类型用于支持多线程的取消请求的。常用的办法是轮询检查一个线程间共享的取消标记的方式,获得取消请求,甚至编写一个线程管理器来增强多线程的可控性。

.NET Framework4.0对多线程和并行运算进行了增强和改进,CancellationTokenSource对象可以在多线程方案中更有效的发出取消请求。

命名空间:System.Threading

程序集:mscorlib.dll

  1. //1.声明一个CancellationTokenSource对象 
  2. private CancellationTokenSource _tokenSource; 
  3. //2.实例化_tokenSource对象 
  4. _tokenSource= new CancellationTokenSource(); 
  5.   
  6. //3.在支持CancellationToken的代码里判断取消请求 
  7. if(_tokenSource.IsCancellationRequested) 
  8.     loop.Stop(); 
  9. //4.在控制线程代码里用调用取消请求 
  10. _tokenSource.Cancel(); 


三、演示

在Wrox出版社的《VisualBasic 2005高级编程》第22章有一个求10000内素数的例子讲解.NET的多线程技术,我写了类似的演示代码,演示6种多线程的方法在后台计算素数,而不阻塞UI。

转:多线程六种多线程方法解决UI线程阻塞第1张

值得一提的是,在任务并发计算素数的演示里,利用CPU多核计算,得到结果所花费的时间有显著的提高。上图中最后一个波峰可看到CPU 0和CPU 1的并行工作情况。

免责声明:文章转载自《转:多线程六种多线程方法解决UI线程阻塞》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇vue之$nextTick详解linux命令--ldconfig和ldd用法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

前端json数据格式化显示

1、格式化处理 1 var obj = "...";//json格式的字符串 2 var jsonPretty = JSON.stringify(JSON.parse(obj),null,2); 2、显示 只需把格式化处理后的json字符串数据放到 pre标签 中即可。 3、demo js代码(直接是对象,所以省略JSON.parse操作) 1 let...

RxJava学习(一)

注意:文字和图片转载自抛物线博客 参考:http://gank.io/post/560e15be2dca930e00da1083 RxJava 到底是什么 一个词:异步。 RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs usin...

spring事务注解@Transactional不生效、不回滚原因

一般情况下在方法上直接添加@Transactional即可,但是极有可能会达不到我们想要的效果,这时就需要添加相应的参数,参数如下。 @Transactional(propagation=Propagation.REQUIRED) //控制事务传播。默认是Propagation.REQUIRED  @Transactional(isolation=Iso...

C#并发编程

最近看C# 并发编程··,这里做一下总结··多线程,异步,并行,大部分都是最近看C#并发编程这个书涉及到的··这里仅仅列出实现方式,书里边介绍的其他的东西没有涉及比如取消操作,同步操作,集合之类的东西 线程:Thread,ThreadPool,BackgroundWorker, Thread 可以又更多控制··ThreadPool就是丢进去系统好管理线程,...

python Zope.interface安装使用

一、接口简述 在我们所熟知的面向对象编程语言中,大多提供了接口(interface)的概念。接口在编程语言中指的是一个抽象类型,是抽象方法的集合;它的特点如下: 1、类通过继承接口的方式,来继承接口的抽象方法; 2、接口并不是类(虽然编写类和方法的方式很相似); 3、类描述对象的属性和方法(实现接口的类,必须实现接口内所描述的所有方法,否则必须声明为抽象类...

神奇的Timer之lock篇

严格的说,这篇叫做lock篇不是太合适,为什么这么说,看完短文就知道了! 大家都对上一篇神奇的Timer中情景2中的示例有很多自己的看法,请允许我今天一一的评说一下吧,说的不对的地方,欢迎拍砖! 1.还是应该写一个5分钟的定时器,只不过在回调函数中检查内容是否有变化! 这个方案是没有问题的,因为RichTextBox中有一个Modified属性,用它可以来...