zoukankan      html  css  js  c++  java
  • 任务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是否成功的执行了。
     
  • 相关阅读:
    Delphi代码获取网卡物理地址三种方法 本文来自Delphi之窗,原文地址:http://www.52delphi.com...
    SQL SERVER 中实现公历到农历的转换
    cxgrid相同列合并显示
    rzCheckList.ItemChecked[Index]就可以判断指定节点地状态.
    为什么PING域名得到IP与实际公网IP不符
    如何让sql server2005和sql server2000共存安装在同一机器上
    如何编译通过 Raize 5.3 中的 RzBorder.pas
    u6升级到u890的过程
    技术部门到底该如何管理才能“和谐”
    在CXGRID中如何让字段能以0.00的格式显示
  • 原文地址:https://www.cnblogs.com/mohanchen/p/8619411.html
Copyright © 2011-2022 走看看