概述
现代程序开发过程中不可避免会使用到多线程相关的技术,之所以要使用多线程,主要原因或目的大致有以下几个:
1、 业务特性决定程序就是多任务的,比如,一边采集数据、一边分析数据、同时还要实时显示数据;
2、 在执行一个较长时间的任务时,不能阻塞UI界面响应,必须通过后台线程处理;
3、 在执行批量计算密集型任务时,采用多线程技术可以提高运行效率。
传统使用的多线程技术有:
- Thread & ThreadPool
- Timer
- BackgroundWorker
目前,这些技术都不再推荐使用了,目前推荐采用基于任务的异步编程模型,包括并行编程和Task的使用。
一、使用Task:
大部分情况下,多线程的应用场景是在后台执行一个较长时间的任务时,不能阻塞界面响应,同时,任务还是可以取消的。
下面我们实现一个简单的示例功能:用户点击Start按钮时启动一个任务,任务执行过程中通过进度条显示任务进度,点击Stop按钮结束任务。
View Code
这个代码写的中规中矩,没什么特别的地方,仅仅是用Tsak取代了早期经常采用的Thread、ThreadPool等,虽然Task内部也是对ThreadPool的封装,但仍然建议尽量采用TASK来实现多任务。
注意:虽然可以通过代码强行结束一个任务,但强烈建议不要这样做,应该给它一个通知让其自己结束。
二、并行编程:
目标:通过一个计算素数的方法,循环计算并打印出10000以内的素数。
计算一个数是否素数的方法:
View Code
如果不采用并行编程,常规实现方法:
for (int i = 1; i <= 10000; i++) { bool b = IsPrimeNumber(i); Console.WriteLine($"{i}:{b}"); }
采用并行编程方法:
Parallel.For(1, 10000, x=> { bool b = IsPrimeNumber(x); Console.WriteLine($"{i}:{b}"); });
运行程序发现时间差异并不大,主要原因是瓶颈在打印控制台上面,去掉打印代码,只保留计算代码,就可以看出性能差异。
Parallel实际是通过线程池进行任务的分配,线程池的最小线程数和最大线程数将影响到整个程序的性能,需要合理设置。(最小线程默认为8。)
ThreadPool.SetMinThreads(10, 10); ThreadPool.SetMaxThreads(20, 20);
按照上述设置,假设线程任务耗时比较长不能很快结束。在启动前面10个线程时速度很快,第10~20个线程就比较慢一点,大约0.5秒,到达20个线程以后,如果前期任务没有结束就不能继续分配任务了。
和Task类似,Parallel类仍然是对ThreadPool的封装,但Parallel有一个优势,它能知道所有任务是否完成,如果采用线程池来实现批量任务,我们需要自己通过计数的方式确定所有子任务是否全部完成。
Parallel类还有一个ForEach方法,使用和For类似,就不重复描述了。
三、 线程(或任务)同步
有时我们需要通知一个任务结束,或一个任务等待某个条件进入下一个状态,这就需要用到任务同步的技术。
一个比较简单的方法就是定义一个变量来表示状态。
private volatile bool CancelWork = false;
后台任务可以轮询该变量进行判断: