虽然使用线程池ThreadPool让我们使用多线程变得容易,但是因为是由系统来分配的,如果想对线程做精细的控制就不太容易了,比如某个线程结束后执行一个回调方法。恰好Task可以实现这样的需求。这篇文章我从以下几点对Task进行总结。
- 认识Task
- Task的用法
Task类在命名空间System.Threading.Tasks下,通过Task的Factory返回TaskFactory类,以TaskFactory.StartNew(Action)方法可以创建一个新的异步线程,所创建的线程默认为后台线程,不会影响前台UI窗口的运行。
如果要取消线程,可以利用CancellationTakenSource对象。如果要在取消任务后执行一个回调方法,则可以使用Task的()方法。
利用Task对之前的例子进行重写和扩展。代码如下。
namespace ThreadDemo{ class Program { static void Main(string[] args) { // 创建CancellationTokenSource对象用于取消Task CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); Fish fish1 = new Fish() { Name = "小黄鱼", Score = 1 }; Fish fish2 = new Fish() { Name = "大鲨鱼", Score = 100 }; // 创建一个Task Task task1 = new Task(() => fish1.Move(cancelTokenSource.Token), cancelTokenSource.Token); task1.Start(); // Task1被取消后的回调方法(小黄鱼被击中后显示积分) task1.ContinueWith(fish1.ShowScore); Task task2 = new Task(() => fish2.Move(cancelTokenSource.Token), cancelTokenSource.Token); task2.Start(); task2.ContinueWith(fish2.ShowScore); // 按任意键发射 Console.ReadKey(); // 武器工厂线程池 Gun gun = new Gun(); LaserGun laserGun = new LaserGun(); TaskFactory taskFactory = new TaskFactory(); Task[] tasks = new Task[] { taskFactory.StartNew(()=>gun.Fire()), taskFactory.StartNew(()=>laserGun.Fire()) }; // 执行武器开火 taskFactory.ContinueWhenAll(tasks, (Task) => { }); cancelTokenSource.Cancel(); Console.ReadKey(); } } ////// 鱼 /// public class Fish { public string Name { get; set; } public int Score { get; set; } public Fish() { } public void Move() { Console.WriteLine(string.Format("{0}在游来游去...", Name)); } ////// 游动 /// /// public void Move(CancellationToken cancelToken) { while (!cancelToken.IsCancellationRequested) { Console.WriteLine(string.Format("{0}在游来游去...", Name)); // 阻塞1秒 Thread.Sleep(1000); } } ////// 中枪后显示奖励 /// /// public void ShowScore(Task task) { Console.WriteLine(string.Format("{0}中弹了,你得到{1}分",Name,Score)); } }}
程序运行结果如下:
eg2:
C# 使用 CancellationTokenSource 终止线程
使用CancellationTokenSource对象需要与Task对象进行配合使用,Task会对当前运行的状态进行控制(这个不用我们关心是如何孔控制的)。而CancellationTokenSource则是外部对Task的控制,如取消、定时取消。
下面我们来看看示例代码
- class Program
- {
- //声明CancellationTokenSource对象
- static CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
- //程序入口
- static void Main(string[] args)
- {
- Task.Factory.StartNew(MyTask, cancelTokenSource.Token);
- Console.WriteLine("请按回车键(Enter)停止");
- Console.ReadLine();
- cancelTokenSource.Cancel();
- Console.WriteLine("已停止");
- Console.ReadLine();
- }
- //测试方法
- static void MyTask()
- {
- //判断是否取消任务
- while (!cancelTokenSource.IsCancellationRequested)
- {
- Console.WriteLine(DateTime.Now);
- Thread.Sleep(1000);
- }
- }
- }
运行效果如图
Task.Factory.StartNew 创建并启动了 MyTask 方法,并传递了一个 CancellationTokenSource.Token 对象进去。我们可以通过在外部CancellationTokenSource对象进行控制是否取消任务的运行。
当在 MyTask 中的 cancelTokenSource.IsCancellationRequested 判断如果是取消了任务的话 就跳出while循环执行。也就结束了任务
我们还可以使用计时取消任务,当一个任务超过了我们所设定的时间然后自动取消该任务的执行。如下代码所示
- var cancelTokenSource = new CancellationTokenSource(3000);
除了构造函数,我们还可以使用另外一种方式实现定时取消,如下代码所示
- cancelTokenSource.CancelAfter(3000);
运行起来效果是一样的,3秒钟定时取消。
多个 CancellationTokenSource 复合
在有多个CancellationTokenSource需要一起并行管理的时候,比如任意一个任务取消 则取消所有任务。我们不必去一个一个的去关闭,只需要将需要一起并行关闭的CancellationTokenSource组合起来就行了。如下代码所示,执行结果是跟上面的图一样的。我就不再上图了。
- class Program
- {
- //声明CancellationTokenSource对象
- static CancellationTokenSource c1 = new CancellationTokenSource();
- static CancellationTokenSource c2 = new CancellationTokenSource();
- static CancellationTokenSource c3 = new CancellationTokenSource();
- //使用多个CancellationTokenSource进行复合管理
- static CancellationTokenSource compositeCancel = CancellationTokenSource.CreateLinkedTokenSource(c1.Token, c2.Token, c3.Token);
- //程序入口
- static void Main(string[] args)
- {
- Task.Factory.StartNew(MyTask, compositeCancel.Token);
- Console.WriteLine("请按回车键(Enter)停止");
- Console.ReadLine();
- //任意一个 CancellationTokenSource 取消任务,那么所有任务都会被取消。
- c1.Cancel();
- Console.WriteLine("已停止");
- Console.ReadLine();
- }
- //测试方法
- static void MyTask()
- {
- //判断是否取消任务
- while (!compositeCancel.IsCancellationRequested)
- {
- Console.WriteLine(DateTime.Now);
- Thread.Sleep(1000);
- }
- }
- }
以上代码调用了c1.Cancel();
触发了MyTask()
方法中的compositeCancel.IsCancellationRequested
为true,则取消了任务。所以我们在所有任务中判断复合的这个CancellationTokenSource对象即可。