CLR via C# 笔记 ----Task(任务) 1

CLR via C# 笔记 ----Task(任务) 1

直接给线程池添加工作项的方式很直接,但是无法得知工作项何时结束,并且不能获得返回值

于是,CLR提供了一个Task类封装了工作项

以下三种写法某种意义上来说是等价的

  //1作为object传递(仅仅是举例的作用),当回调需求一个ojbect参数时,需要额外传递这个参数
        ThreadPool.QueueUserWorkItem(DoSth,1);
        new Task(DoSth, 1).Start();
        Task.Run(()=>DoSth(1));

 public void DoSth(object o) {  /*DoSth*/}

这三种写法都无法获取返回值,只是为了展示Task和线程池工作项之间的关系

实际的Task用法可能是这样

 public int DoSth()
    {
        //做一些耗时工作
        Thread.Sleep(2000);
        //返回一个结果
        return 1;
    } 
   Task<int> task1 = new Task<int>(DoSth);
  //task创建好后,可以在任意时刻start
   task1.Start();
  //调用task的Result属性 获取返回值 通常还需要try catch处理 先略过
   Console.WriteLine(task1.Result);

很好,可以获取工作项的返回值了,但是这种获取方式有问题,直接调用task的Result属性 会造成调用线程阻塞 (需要等待工作项完成)

这和多线程使用的初衷根本不相符。

所以Task有一个方法用来注册任务完成时的回调(延续任务),参数是task本身

 task1.ContinueWith(t => Console.WriteLine(t.Result));

调用这个方法不会造成任何阻塞,而传进去的回调 在工作项结束后会被另一个线程执行

同时,还可额外传递一个TaskContinuationOptions类型的枚举,来指定此延续任务的执行方式

  [Flags]
    public enum TaskContinuationOptions
    {
        None = 0,//默认
        PreferFairness = 1,//提意快速执行 (因为延续任务本质上还是任务,会由线程池的调度器调度)
        LongRunning = 2,//书中描述 提意由尽可能由线程池线程执行 (但上文又说延续任务都由线程池负责...)
        AttachedToParent = 4,//将延续任务作为上一个任务的子任务
        DenyChildAttach = 8,//拒绝Attach (以此延续任务为父任务)
        HideScheduler = 16,//启用默认调度器 (任务可以指定一个调度器,默认情况下,延续任务使用上一个任务的调度器)
        LazyCancellation = 32,//上一个任务被取消了的话,本延续任务取消
        RunContinuationsAsynchronously = 64,//用其他线程执行延续任务
        NotOnRanToCompletion = 65536,
        NotOnFaulted = 131072,
        OnlyOnCanceled = 196608,//只有在上一个任务取消的时候 才执行
        NotOnCanceled = 262144,
        OnlyOnFaulted = 327680,//上一个任务异常时才执行
        OnlyOnRanToCompletion = 393216,//上一个任务顺利完成时才执行
        ExecuteSynchronously = 524288//同步执行延续任务(但不阻塞 原理不明..)
    }

如果不指定标志,延续任务总会执行

最后提一嘴子任务,子任务就是在任务的函数中 又创建的任务,并且在TaskCreationOptions.AttachedToParent(注意这个枚举和上面那个不同...枚举中的标志部分相同,毕竟本质上都是在创建一个任务)(默认任何地方创建的任务都是顶级任务,除非指定标志)

父任务需要等待子任务全部完成才能算完成。