任务Task系列之Task初认识

  说起多线程,想到更多的是线程Thread,那么我们今天说的任务Task和线程有什么关系呢?
  • 任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行;
  • 线程和任务并不是一对一的关系,比如开10个任务并不是说就会开10个线程,这一点任务和线程池有点类似,但是任务相比线程池开销更小控制也更为精确;
  关于Task
  Task标识一个异步操作,创建Task有两种方法,一种类似于创建对象直接new出来,一种是通过工厂创建。
 public void CreateTask()
        {
            //直接实例化
            var task1 = new Task(() =>
            {
                Console.WriteLine("任务1创建成功!");
            });

            task1.Start();

            //通过静态工厂创建
            var task2 = Task.Factory.StartNew(() => {

                Console.WriteLine("任务2创建成功!");            
            });
        }
下面来看一下一个Task的生命周期,编写测试代码:
public void GetTaskStatus()
        {
            //直接实例化
            var task1 = new Task(() =>
            {
                Console.WriteLine("开始");
                Thread.Sleep(10000);
                Console.WriteLine("结束");
            });

            Console.WriteLine(task1.Status);

            task1.Start();

            Console.WriteLine(task1.Status);

            task1.Wait();

            Console.WriteLine(task1.Status);
        }

  运行结果:

由此可见 一个Task的生命周期中至少包含以下三个阶段:
  • Created: Task被创建
  • WaitingToRun :等待执行
  • RanToCompletion: 任务执行完成
其实至少应该还包含一个阶段:Running表示运行中。
下面谈谈Task的任务控制,这也是Task的优势所在,可以使以前无法控制的并行过程按照自己的需求变得有序可控起来。
Task.Wait
上面的示例中已经用过了,就是等待任务执行完成,阻塞主线程,多用于线程(任务)与主线程同步。
Task.WaitAll
顾名思义,多个任务并行执行时,等待所有任务执行完成,实例代码:
public void WaitAllTask()
        {
            var task1 = new Task(() =>
            {
                Console.WriteLine("任务一开始!");
                Thread.Sleep(2000);
                Console.WriteLine("任务一结束!");
            });
            var task2 = new Task(() =>
            {
                Console.WriteLine("任务二开始!");
                Thread.Sleep(2000);
                Console.WriteLine("任务二结束!");
            });
            task1.Start();
            task2.Start();
            Task.WaitAll(task1,task2);
            Console.WriteLine("所有任务执行完成!");
        }

  执行结果:

  

  

可见等到两个任务全部结束后,主线程才提示任务结束。
Task.WaitAny
等待任意一个任务执行结束,上面的示例代码修改下:
  
task1.Start();
  task2.Start();
  Task.WaitAny(task1, task2);
  Console.WriteLine("有任务执行完成!");
执行结果:
可见任务一结束后主线程就提示有任务完成了。
Task.ContinueWith
一个Task任务后自动开启另一个Task,实现Task的延续。实例代码如下:
public void TaskContinue()
        {
            var task1 = new Task(() =>
            {
                Console.WriteLine("任务一开始!");
                Thread.Sleep(2000);
                Console.WriteLine("任务一结束!");
            });
            var task2 = new Task(() =>
            {
                Console.WriteLine("任务二开始!");
                Thread.Sleep(20000);
                Console.WriteLine("任务二结束!");
            });
            task1.Start();
            task2.Start();

            var result = task1.ContinueWith(task =>
            {
                Console.WriteLine("任务一完成");
                return String.Format("任务一结束后状态{0}",task.Status);
            });
            
            Console.WriteLine(result.Result.ToString());
        }
运行结果:
Task的取消
前面说了那么多Task的用法,下面我们来看Task的取消,比如我们启动了一个Task,出现了遗产或者用户主动选择取消,我们是可以取消这个任务的。在任务Task中,我们通过calcellation的tokens来取消一个Task,有些时候在Task的Body里面会包含循环,我们可以在轮询的时候判断IscancellationRequested的属性是否为True,如果为True的时候就return或者抛出异常。下面在代码中我们看下如何实现任务的取消:
public void TaskCancel()
        {
            var tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;

            var task = Task.Factory.StartNew(() => {

                Int32 sec = 0;
                while (true)
                {
                    
                    if (token.IsCancellationRequested)
                    {
                        Console.WriteLine("任务被终止,即将退出");
                        break;
                    }
                    else
                    {
                        Console.WriteLine("计时{0}s", sec);
                    }
                    Thread.Sleep(1000);
                    sec += 1;
                }
            },token);

            token.Register(() => {
                
                Console.WriteLine("任务取消");
            });

            Console.WriteLine("按任意键退出计时");
            Console.ReadKey();
            tokenSource.Cancel();
        }
这里开启了一个Task,并给token注册了一个方法,输出信息,然后等待用户输入,用户输入任意信息后,执行tokenSource.Cancel方法,任务随即被取消,执行结果如下:
Task的嵌套
Task中还可以再嵌套Task,Task中的嵌套可以分为两种。关联嵌套和非关联嵌套,也就是说内层的Task和外层的Task是否有关联,下面我们代码示例来说明:
public void TaskWithTask()
        {
            var pTask = Task.Factory.StartNew(() => {
                
                var cTask = Task.Factory.StartNew(() => {

                    Thread.Sleep(1000);
                    Console.WriteLine("子任务完成");
                });

                Console.WriteLine("父任务完成");
            });

            pTask.Wait();
            Console.WriteLine("非关联嵌套");
        }

  运行结果:

  

由执行结果可知,外层的pTask运行完成后,并不会等待内层的cTask,直接向下走了,其实这种嵌套相当于我们创建了两个Task,只是写到一起,代码可读性更强,并且可以在一个Task中加入多个Task,让进度并行前进。
再看一个实例:
public void TaskWithTask()
        {
            var pTask = Task.Factory.StartNew(() =>
            {

                var cTask = Task.Factory.StartNew(() =>
                {

                    Thread.Sleep(1000);
                    Console.WriteLine("子任务完成");
                },TaskCreationOptions.AttachedToParent);
                Console.WriteLine("父任务完成");
            });

            pTask.Wait();
            Console.WriteLine("非关联嵌套");
        }
我们在创建cTask的时候,加入了参数TaskCreationOptions.AttachedToParent,这个时候pTask和cTask就产生了关联,cTask就会成为pTask的一部分,执行结果:
可见tTask会等待cTask执行完成。
下面我们来看一个task综合使用的例子,看一些多任务是如何协作的,假设这样一个场景:任务2和任务3要等待任务1完成后,取得任务1的结果,然后开始执行。任务4要等待任务2完成,取得其结果才能执行,最终任务3和任务4都完成了,合并结果,任务完成。
代码如下:
public void MutiTasks()
        {
            var t1 = Task.Factory.StartNew<Int32>(() => {
                
                Console.WriteLine("任务一执行");
                return 1;
            });
            t1.Wait();

            var t3 = Task.Factory.StartNew<Int32>(() => {
                
                Console.WriteLine("任务三执行");
                return t1.Result+3;
            });

            var t4 = Task.Factory.StartNew<Int32>(() =>
            {

                Console.WriteLine("任务二执行");
                return t1.Result + 2;
            }).ContinueWith<Int32>(task => {

                Console.WriteLine("任务四执行");
                return task.Result + 4;
            });

            Task.WaitAll(t3,t4);

            Console.WriteLine(t3.Result + t4.Result);
        }

  运行结果:

  

Task的异常处理
任何应用程序都需要异常处理机制,谁也无法保证自己写得代码在任何时候都是可以正常运行的,那么在Task中到底该怎么处理异常呢?我们先尝试一下用try-catch
public void TaskExcetion()
        {
            try
            {
                var pTask = Task.Factory.StartNew(() =>
                {
                    var cTask = Task.Factory.StartNew(() =>
                    {
                        Thread.Sleep(1000);
                        throw new Exception("子任务异常");
                        Console.WriteLine("子任务完成");
                    });
                    throw new Exception("父任务异常");
                    Console.WriteLine("父任务完成");
                });

                pTask.Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.WriteLine("非关联嵌套");
        }
看一下结果:
可见异常信息并不对,其实正确的姿势是这个样子的:
public void TaskAggregateExcetion()
        {
            try
            {
                var pTask = Task.Factory.StartNew(() =>
                {
                    var cTask = Task.Factory.StartNew(() =>
                    {
                        Thread.Sleep(1000);
                        throw new Exception("子任务异常");
                        Console.WriteLine("子任务完成");
                    });
                    throw new Exception("父任务异常");
                    Console.WriteLine("父任务完成");
                });

                pTask.Wait();
            }
            catch (AggregateException ex)
            {
                foreach (Exception item in ex.InnerExceptions)
                {
                    Console.WriteLine(item.Message);
                }
            }
            Console.WriteLine("非关联嵌套");
        }

  运行结果:

  

  

这里用了AggregateException,就是异常集合,当然开发中不会只有一个线程,肯定会有多个线程,多个线程就可能有多个异常。当然,除了在task中使用异常,我们还可以通过Task的几个属性来判断Task的状态,如:IsCompleted, IsFaulted, IsCancelled,Exception等等来判断task是否成功的执行了。
 
原文地址:https://www.cnblogs.com/mohanchen/p/8619411.html