澳门新萄京官方网站-www.8455.com-澳门新萄京赌场网址

Net多线程编程,中TASK类的使用

2019-05-05 作者:www.8455.com   |   浏览(84)

任务概述

线程(Thread)是创建并发的底层工具,因此有一定的局限性(不易得到返回值(必须通过创建共享域);异常的捕获和处理也麻烦;同时线程执行完毕后无法再次开启该线程),这些局限性会降低性能同时影响并发性的实现(不容易组合较小的并发操作实现较大的并发操作,会增加手工同步处理(加锁,发送信号)的依赖,容易出现问题)。

线程池的(ThreadPool)QueueUserWorkItem方法很容发起一次异步的计算限制操作。但这个技术同样有着许多限制,最大的问题是没有内建的机制让你知道操作在什么时候完成,也没有机制在操作完成时获得返回值。

Task类可以解决上述所有的问题。

任务(Task)表示一个通过或不通过线程实现的并发操作,任务是可组合的,使用延续(continuation)可将它们串联在一起,它们可以使用线程池减少启动延迟,可使用回调方法避免多个线程同时等待I/O密集操作。

 

然而,在今天这篇博客中,我们要知道的是,QueueUserWorkItem这个技术存在许多限制。其中最大的问题是没有一个内建的机制让你知道操作在什么时候完成,也没有一个机制在操作完成是获得一个返回值,这些问题使得我们都不敢启用这个技术。

随着 .NET 4.0的到来,她与以前各版本的一个明显差别就是并行功能的增强,以此来适应这个多核的世界。于是引入了一个新概念---任务,作为支持并行运算的重要组成部分,同时,也作为对线程池的一个补充和完善。从所周知,使用线程池有两个明显的缺点,那就是一旦把我们要执行的任务放进去后,什么时候执行完成,以及执行完成后需要返回值,我们都无法通过内置的方式而得知。由于任务(Task)的推出,使得我们对并行编程变得简单,而且不用关心底层是怎么实现的,由于比线程池更灵活,如果能掌握好Task,对于写出高效的并行代码非常有帮助。

前言

1 System.Threading.Tasks.Task简介

基础任务(Task)

微软在.NET 4.0 引入任务(Task)的概念。通过System.Threading.Tasks命名空间使用任务。它是在ThreadPool的基础上进行封装的。Task默认都是使用池化线程,它们都是后台线程,这意味着主线程结束时其它任务也会随之停止。

启动一个任务有多种方式,如以下示例:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine("主线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
 6             int workerThreadsCount, completionPortThreadsCount;
 7             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
 8             Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
 9             //第一种:实例化方式Start启动
10             {
11                 Task task = new Task(() =>
12                 {
13                     Test("one-ok");
14                 });
15                 task.Start();
16             }
17             //第二种:通过Task类静态方法Run方式进行启动
18             {
19                 Task.Run(() =>
20                 {
21                     Test("two-ok");
22                 });
23             }
24             //第三种:通过TaskFactory的StartNew方法启动
25             {
26                 TaskFactory taskFactory = new TaskFactory();
27                 taskFactory.StartNew(() =>
28                 {
29                     Test("three-ok");
30                 });
31             }
32             //第四种:.通过Task.Factory进行启动
33             {
34                 Task taskStarNew = Task.Factory.StartNew(() =>
35                 {
36                     Test("four-ok");
37                 });
38             }
39             //第五种:通过Task对象的RunSynchronously方法启动(同步,由主线程执行,会卡主线程)
40             {
41                 Task taskRunSync = new Task(() =>
42                 {
43                     Console.WriteLine("线程Id:{0},执行方法:five-ok", Thread.CurrentThread.ManagedThreadId);
44                 });
45                 taskRunSync.RunSynchronously();
46             }
47             Thread.Sleep(1000);
48             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
49             Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
50             Console.ReadKey();
51         }
52         static void Test(string o)
53         {
54             Thread.Sleep(2000);
55             Console.WriteLine("线程Id:{0},执行方法:{1}", Thread.CurrentThread.ManagedThreadId, o);
56         }
57         /*
58          * 作者:Jonins
59          * 出处:http://www.cnblogs.com/jonins/
60          */
61     }

执行结果:

图片 1

上面示例中除去使用RunSynchronously方法启动的是同步任务(由启用的线程执行任务)外,其它几种方式内部都由线程池内的工作者线程处理。

说明

1.事实上Task.Factory类型本身就是TaskFactory(任务工厂),而Task.Run(在.NET4.5引入,4.0版本调用的是后者)是Task.Factory.StartNew的简写法,是后者的重载版本,更灵活简单些。

2.调用静态Run方法会自动创建Task对象并立即调用Start

3.如Task.Run等方式启动任务并没有调用Start,因为它创建的是“热”任务,相反“冷”任务的创建是通过Task构造函数。

 

Microsoft为了克服这些限制(同时解决其他一些问题),引入了任务(tasks)的概念。顺带说一下我们得通过System.Threading.Tasks命名空间来使用它们。

一、新建任务

学习这件事情是一个习惯,不能停。。。另外这篇已经看过两个月过去,但觉得有些事情不总结跟没做没啥区别,遂记下此文

一个Task表示一个异步操作,Task的创建和执行是独立的。

返回值(Task<TResult>)&状态(Status)

Task有一个泛型子类Task<TResult>,它允许任务返回一个值。调用Task.Run,传入一个Func<Tresult>代理或兼容的Lambda表达式,然后查询Result属性获得结果。如果任务没有完成,那么访问Result属性会阻塞当前线程,直至任务完成

1     public static Task<TResult> Run<TResult>(Func<TResult> function);

而任务的Status属性可用于跟踪任务的执行状态,如下所示:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task<int> task = Task.Run(() =>
 6             {
 7                 int total = 0;
 8                 for (int i = 0; i <= 100; i  )
 9                 {
10                     total  = i;
11                 }
12                 Thread.Sleep(2000);
13                 return total;
14             });
15             Console.WriteLine("任务状态:{0}",task.Status);
16             Thread.Sleep(1000);
17             Console.WriteLine("任务状态:{0}", task.Status);
18             int totalCount = task.Result;//如果任务没有完成,则阻塞
19             Console.WriteLine("任务状态:{0}", task.Status);
20             Console.WriteLine("总数为:{0}",totalCount);
21             Console.ReadKey();
22         }
23     }

执行如下:

 图片 2

Reulst属性内部会调用Wait(等待);

任务的Status属性是一个TaskStatus枚举类型:

1  public TaskStatus Status { get; }

说明如下:

枚举值 说明
Canceled

任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;

或者在该任务开始执行之前,已向该任务的 CancellationToken 发出了信号。

Created 该任务已初始化,但尚未被计划。
Faulted 由于未处理异常的原因而完成的任务。
RanToCompletion 已完成执行的任务。
Running 任务正在运行,尚未完成。
WaitingForActivation 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。
WaitingForChildrenToComplete 该任务已完成执行,正在隐式等待附加的子任务完成。
WaitingToRun 该任务已被计划执行,但尚未开始执行。

 

现在我要说的是,用线程池不是调用ThreadPool的QueueUserWorkItem方法,而是用任务来做相同的事:

      在System.Threading.Tasks命名空间下,有两个新类,Task及其泛型版本Task<TResult>,这两个类是用来创建任务的,如果执行的代码不需要返回值,请使用Task,若需要返回值,请使用Task<TResult>。

1.CLR线程池基础

只读属性:

任务集合返回值(WhenAll&WhenAny)

 Task中有非常方便的对并行运行的任务集合获取返回值的方式,比如WhenAllWhenAny

复制代码 1        static void Main(string[] args) 

      创建任务的方式有两种,一种是通过Task.Factory.StartNew方法来创建一个新任务,如:

2.ThreadPool的简单使用练习

返回值

名称

说明

object

AsyncState

表示在创建任务时传递给该任务的状态数据

TaskCreationOptions

CreationOptions

获取用于创建此任务的 TaskCreationOptions

 

CurrentId

当前正在执行 Task 的 ID

AggregateException

Exception

获取导致 AggregateException 提前结束的 Task。如果 Task 成功完成或尚未引发任何异常,则返回 null

TaskFactory

Factory

提供对用于创建 Task 和 Task<TResult> 的工厂方法的访问

int

Id

获取此 Task 实例的 ID

bool

IsCanceled

指明此 Task 实例是否由于被取消的原因而已完成执行

bool

IsCompleted

指明此 Task 是否已完成

bool

IsFaulted

指明Task 是否由于未经处理异常的原因而完成

TaskStatus

Status

获取此任务的 TaskStatus

1.WhenAll

WhenAll:等待提供的所有 Task 对象完成执行过程(所有任务全部完成)。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             List<Task<int>> taskList = new List<Task<int>>();//声明一个任务集合
 6             TaskFactory taskFactory = new TaskFactory();
 7             for (int i = 0; i < 5; i  )
 8             {
 9                 int total = i;
10                 Task<int> task = taskFactory.StartNew(() => Test(total));
11                 taskList.Add(task);//将任务放进集合中
12             }
13             Console.WriteLine("主线程Id:{0},继续执行A.....", Thread.CurrentThread.ManagedThreadId);
14             Task<int[]> taskReulstList = Task.WhenAll(taskList);//创建一个任务,该任务将集合中的所有 Task 对象都完成时完成
15             for (int i = 0; i < taskReulstList.Result.Length; i  )//这里调用了Result,所以会阻塞线程,等待集合内所有任务全部完成
16             {
17                 Console.WriteLine("返回值:{0}", taskReulstList.Result[i]);//遍历任务集合内Task返回的值
18             }
19             Console.WriteLine("主线程Id:{0},继续执行B.....", Thread.CurrentThread.ManagedThreadId);
20             Console.ReadKey();
21         }
22         private static int Test(int o)
23         {
24             Console.WriteLine("线程Id:{0},Task执行成功,参数为:{1}", Thread.CurrentThread.ManagedThreadId, o);
25             Thread.Sleep(500 * o);
26             return o;
27         }
28     }

执行结果:

图片 3

2        { 

      Task task = Task.Facotry.StartNew(()=>Console.WriteLine(“Hello, World!”));//此行代码执行后,任务就开始执行

3.执行上下文

 

2.WhenAny

WhenAny:等待提供的任一 Task 对象完成执行过程(只要有一个任务完成)。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             List<Task<int>> taskList = new List<Task<int>>();//声明一个任务集合
 6             TaskFactory taskFactory = new TaskFactory();
 7             for (int i = 0; i < 5; i  )
 8             {
 9                 int total = i;
10                 Task<int> task = taskFactory.StartNew(() => Test(total));
11                 taskList.Add(task);//将任务放进集合中
12             }
13             Console.WriteLine("主线程Id:{0},继续执行A.....", Thread.CurrentThread.ManagedThreadId);
14             Task<Task<int>> taskReulstList = Task.WhenAny(taskList);//创建一个任务,该任务将在集合中的任意 Task 对象完成时完成
15             Console.WriteLine("返回值:{0}", taskReulstList.Result.Result);//得到任务集合内最先完成的任务的返回值
16             Console.WriteLine("主线程Id:{0},继续执行B.....", Thread.CurrentThread.ManagedThreadId);
17             Console.ReadKey();
18         }
19         private static int Test(int o)
20         {
21             Console.WriteLine("线程Id:{0},Task执行成功,参数为:{1}", Thread.CurrentThread.ManagedThreadId, o);
22             Thread.Sleep(500 * o);
23             return o;
24         }
25     }

执行结果(这里返回值肯定会是0,因为休眠最短):

图片 4

 

3            Console.WriteLine("主线程启动"); 

      另一种方法是通过Task类的构造函数来创建一个新任务,如:

4.协作式取消和超时,System.Threading.CancellationTokenSource的简单使用

2 Task状态和生命周期

等待(Wait)&执行方式(TaskCreationOptions)

4            //ThreadPool.QueueUserWorkItem(StartCode,5); 

      Task task = new Task(()=>Console.WriteLine(“Hello, World!”));//此处只把要完成的工作交给任务,但任务并未开始

5.任务

一个Task实例只会完成其生命周期一次,当Task达到它的3种可能的最终状态之一时,它就再也回不去之前的状态了。任务的生命周期从TaskStatus.Created状态真正开始。

1.任务等待(Wait)

调用任务的Wait方法可以阻塞任务直至任务完成,类似于线程的join。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task task = Task.Run(() =>
 6             {
 7                 Console.WriteLine("线程执行Begin");
 8                 Thread.Sleep(2000);
 9                 Console.WriteLine("线程执行End");
10             });
11             Console.WriteLine("任务是否完成:{0}", task.IsCompleted);
12             task.Wait();//阻塞,直至任务完成
13             Console.WriteLine("任务是否完成:{0}", task.IsCompleted);
14             Console.ReadKey();
15         }
16     }

执行如下:

图片 5

注意

线程调用Wait方法时,系统检测线程要等待的Task是否已经开始执行。如果是线程则会阻塞直到Task运行结束为止。但如果Task还没有开始执行任务,系统可能(取决于TaskScheduler)使用调用Wait的线程来执行Task,这种情况下调用Wait的线程不会阻塞,它会执行Task并立即返回。好处在于没有线程会被阻塞,所以减少了资源占用。不好的地方在于加入线程在调用Wait前已经获得了一个线程同步锁,而Task试图获取同一个锁,就会造成死锁的线程。

5            new Task(StartCode, 5).Start();

      task.Start();//调用Start方法后,任务才会在将来某个时候开始执行。

6.任务调度器

1) 初始状态

2.任务执行方式(TaskCreationOptions)

我们知道为了创建一个Task,需要调用构造函数并传递一个Action或Action<object>委托,如果传递的是期待一个Object的方法,还必须向Task的构造函数穿都要传给操作的实参。还可以选择向构造器传递一些TaskCreationOptions标记来控制Task的执行方式。

 TaskCreationOptions为枚举类型

枚举值 说明
None 默认。
PreferFairness 尽可能公平的方式安排任务,即先进先执行。
LongRunning 指定任务将是长时间运行的,会新建线程执行,不会使用池化线程。
AttachedToParent 指定将任务附加到任务层次结构中的某个父级
DenyChildAttach 任务试图和这个父任务连接将抛出一个InvalidOperationException
HideScheduler 强迫子任务使用默认调度而非父级任务调度

在默认情况下,Task内部是运行在池化线程上,这种线程会非常适合执行短计算密集作业。如果要执行长阻塞操作,则要避免使用池化线程。

在池化线程上运行一个长任务问题不大,但是如果要同时运行多个长任务(特别是会阻塞的任务),则会对性能产生影响。最好使用:TaskCreationOptions.LongRunning。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int workerThreadsCount, completionPortThreadsCount;
 6             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
 7             Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1},主线程Id:{2}", workerThreadsCount, completionPortThreadsCount, Thread.CurrentThread.ManagedThreadId);
 8             Task task = Task.Factory.StartNew(() =>
 9             {
10                 Console.WriteLine("长任务执行,线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
11                 Thread.Sleep(2000);
12             }, TaskCreationOptions.LongRunning);
13             Thread.Sleep(1000);
14             ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
15             Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1},主线程Id:{2}", workerThreadsCount, completionPortThreadsCount, Thread.CurrentThread.ManagedThreadId);
16             Console.ReadKey();
17         }
18     }

执行结果如下:

图片 6

注意

如果使运行I/O密集任务,则可以使用TaskCompletionSource和异步函数(asynchronous functions),通过回调(延续)实现并发性,而是不通过线程实现。

如果使运行计算密集性任务,则可以使用一个生产者/消费者队列,控制这些任务的并发数量,避免出现线程和进程阻塞的问题。

 

 6            Console.WriteLine("主线程运行到此!"); 

      同时,我们可以调用Wait方法来等待任务的完成或者调用IsCompleted属性来判断任务是否完成。需要说明的是,两种创建任务的方法都可以配合TaskCreationOptions枚举来实现我们对任务执行的行为具体控制, 同时,这两种创建方式允许我们传递一个TaskCreationOptions对象来取消正在运行中的任务,请看任务的取消。

一、CLR线程池基础

Task实例有三种可能的初始状态

延续(continuation)&延续选项(TaskContinuationOptions)

延续(continuation)会告诉任务在完成后继续执行下面的操作。延续通常由一个回调方法实现,它会在操作完成之后执行一次。给一个任务附加延续的方法有两种

7            Thread.Sleep(1000); 

二、任务的取消

如26章所述,创建和销毁线程是一个昂贵的操作,要耗费大量的时间。另外太多的线程会浪费内存资源。由于操作系统必须调度可运行的线程并执行上下文切换,所以太多的线程还对性能不利。

说明

TaskStatus.Created

该任务已初始化,但尚未被计划。使用Task构造函数创建Task实例时的初始状态。

TaskStatus.WaitingForActivation

该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。一个任务的初始状态,这个任务只有当其依赖的任务完成之后才会被调度。

TaskStatus.WaitingToRun

该任务已被计划执行,但尚未开始执行。使用TaskFactory.StartNew创建的任务的初始状态。

1.GetAwaiter

任务的方法GetAwaiter是Framework 4.5新增加的,而C# 5.0的异步功能使用了这种方法,因此它非常重要。给一个任务附加延续如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task<int> task = Task.Run(() =>
 6              {
 7                  int total = 0;
 8                  for (int i = 0; i <= 100; i  )
 9                  {
10                      total  = i;
11                  }
12                  Thread.Sleep(2000);
13                  return total;
14              });
15             var awaiter = task.GetAwaiter();
16             awaiter.OnCompleted(() =>
17             {
18                 int result = awaiter.GetResult();//在延续中获取Task的执行结果
19                 Console.WriteLine(result);
20             });
21             Console.ReadKey();
22         }
23     }

执行结果控制台会打印:5050。

调用GetAwaiter会返回一个等待者(awaiter)对象,它会让先导(antecedent)任务在任务完成(或出错)之后执行一个代理。已经完成的任务也可以附加一个延续,这事延续会马上执行。

注意

1.等待者(awaiter)可以是任意对象,但必须包含特定的两个方法和一个Boolean类型属性。

1   public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion
2     {
3         public bool IsCompleted { get; }
4         public TResult GetResult();
5         public void OnCompleted(Action continuation);
6     }

2.先导任务出现错误,那么当延续代码调用awaiter.GetResult()时就会重新抛出异常。我们可以需要调用GetResult,而是直接访问先导任务的Result属性(task.Result)。

GetResult的好处是,当先导任务出现错误时,异常可以直接抛出而不封装在AggregateException中。

3.如果出现同步上下文,那么会自动捕捉它,然后延续提交到这个上下文中。在无需同步上下文的情况下通常不采用这种方法,使用ConfigureAwait代替它。它通常会使延续运行在先导任务所在的线程上,从而避免不必要的过载。

1    var awaiter = task.ConfigureAwait(false).GetAwaiter();

8        } 

     这世界唯一不变的就是变化,当外部条件发生变化时,我们可能会取消正在执行的任务。对于.NET 4.0之前,.NET并未提供一个内置的解决方案来取消线程池中正在执行的代码,但在.NET 4.0中,我们有了Cooperative Cancellation模式,这使得取消正在执行的任务变得非常简单。如下所示:

为了改善这个情况,CLR包含了代码管理它自己的线程池(thread pool),线程池是你的应用程序能使用的线程的集合。

2)中间状态

2.ContinueWith

另一种附加延续的方法是调用任务的ContinueWith方法:

 1         static void Main(string[] args)
 2         {
 3             Task<int> task = Task.Run(() =>
 4             {
 5                 int total = 0;
 6                 for (int i = 0; i <= 100; i  )
 7                 {
 8                     total  = i;
 9                 }
10                 Thread.Sleep(2000);
11                 return total;
12             });
13             task.ContinueWith(continuationAction =>
14             {
15                 int result = continuationAction.Result;
16                 Console.WriteLine(result);
17             });
18             Console.ReadKey();
19         }

ContinueWith本身会返回一个Task,它非常适用于添加更多的延续。然后如果任务出现错误,我们必须直接处理AggregateException。

如果想让延续运行在统一个线程上,必须指定 TaskContinuationOptions.ExecuteSynchronously;否则它会弹回线程池。ContinueWith特别适用于并行编程场景。

9 10        private static void StartCode(object i)

using System; 
using System.Threading; 
using System.Threading.Tasks;

namespace TaskDemo 

    class Program 
    { 
        static void Main() 
        { 
            CancellationTokenSource cts = new CancellationTokenSource(); 
            Task t = new Task(() => LongRunTask(cts.Token)); 
            t.Start(); 
            Thread.Sleep(2000); 
            cts.Cancel(); 
            Console.Read(); 
        }

        static void LongRunTask(CancellationToken token) 
        {

             //此处方法模拟一个耗时的工作 
            for (int i = 0; i < 1000; i ) 
            { 
                if (!token.IsCancellationRequested) 
                { 
                    Thread.Sleep(500); 
                    Console.Write("."); 
                } 
                else 
                { 
                    Console.WriteLine("任务取消"); 
                    break; 
                } 
            } 
        } 
    } 
}

每CLR一个线程池,这个线程池由CLR控制的所有AppDomain共享。

Task实例有两种可能的中间状态

3.延续选项(TaskContinuationOptions)

在使用ContinueWith时可以指定任务的延续选项即TaskContinuationOptions,它的前六个枚举类型与之前说的TaskCreationOptions枚举提供的标志完全一样,补充后续几个枚举值:

枚举值 说明
LazyCancellation 除非先导任务完成,否则禁止延续任务完成(取消)。
NotOnRanToCompletion 指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。
NotOnFaulted 指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。
NotOnCanceled 指定不应在延续任务前面的任务已取消的情况下安排延续任务。 
OnlyOnCanceled 指定只应在延续前面的任务已取消的情况下安排延续任务。
OnlyOnFaulted 指定只有在延续任务前面的任务引发了未处理异常的情况下才应安排延续任务。
OnlyOnRanToCompletion 指定只有在延续任务前面的任务引发了未处理异常的情况下才应安排延续任务。
ExecuteSynchronously 指定希望由先导任务的线程执行,先导任务完成后线程继续执行延续任务。

 

ExecuteSynchronously是指同步执行,两个任务都在同一个=线程一前一后的执行。

ContinueWith结合TaskContinuationOptions使用的示例:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Task<int> task = Task.Run(() =>
 6             {
 7                 int total = 0;
 8                 for (int i = 0; i <= 100; i  )
 9                 {
10                     total  = i;
11                 }
12                 if (total == 5050)
13                 {
14                     throw new Exception("错误");//这段代码可以注释或开启,用于测试
15                 }
16                 return total;
17             });
18             //指定先导任务无报错的延续任务
19             task.ContinueWith(continuationAction =>
20             {
21                 int result = continuationAction.Result;
22                 Console.WriteLine(result);
23             }, TaskContinuationOptions.NotOnFaulted);
24             //指定先导任务报错时的延续任务
25             task.ContinueWith(continuationAction =>
26             {
27                 foreach (Exception ex in continuationAction.Exception.InnerExceptions)//有关AggregateException异常处理后续讨论
28                 {
29                     Console.WriteLine(ex.Message);
30                 }
31             }, TaskContinuationOptions.OnlyOnFaulted);
32             Console.ReadKey();
33         }
34     }

执行结果会打印:报错,如果注释掉抛出异常的代码则会打印5050。

 

11        {

 

CLR初始化时,线程池中是没有线程的。在内部,线程池维护了一个操作请求队列。应用程序执行一个异步操作时,就调用某个方法,将一个记录项(entry)追加到线程池的队列中,线程池的代码从这个队列中提取记录项,将这个记录项派发(dispatch)给一个线程池线程。如果线程池中没有线程,就创建一个新线程。

说明

TaskStatus.Running

该任务正在运行,但尚未完成

TaskStatus.WaitingForChildrenToComplete

该任务已完成执行,正在隐式等待附加的子任务完成

TaskCompletionSource

另一种创建任务的方法是使用TaskCompletionSource。它允许创建一个任务,并可以任务分发给使用者,并且这些使用者可以使用该任务的任何成员。它的实现原理是通过一个可以手动操作的“附属”任务,用于指示操作完成或出错的时间。

TaskCompletionSource的真正作用是创建一个不绑定线程的任务(手动控制任务工作流,可以使你把创建任务和完成任务分开)

这种方法非常适合I/O密集作业:可以利用所有任务的优点(它们能够生成返回值、异常和延续),但不会在操作执行期间阻塞线程。

例如,假设一个任务需要等待2秒,然后返回10,我们的方法会返回在一个2秒后完成的任务,通过给任务附加一个延续就可以在不阻塞任何线程的前提下打印这个结果,如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var awaiter = Demo(2000).GetAwaiter();//得到任务通过延续输出返回值
 6             awaiter.OnCompleted(() =>
 7             {
 8                 Console.WriteLine(awaiter.GetResult());
 9             });
10             Console.WriteLine("主线程继续执行....");
11             Console.ReadKey();
12         }
13         static Task<int> Demo(int millis)
14         {
15             //创建一个任务完成源
16             TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
17             var timer = new System.Timers.Timer(millis) { AutoReset = false };
18             timer.Elapsed  = delegate
19             {
20                 timer.Dispose(); taskCompletionSource.SetResult(10);//写入返回值
21             };
22             timer.Start();
23             return taskCompletionSource.Task;//返回任务
24         }
25     }

执行结果:

图片 7

注意:如果多次调用SetResult、SetException或SetCanceled,它们会抛出异常,而TryXXX会返回false。

 

12            Console.WriteLine("开始执行子线程...{0}",i);

三、任务的异常机制

如果应用程序向线程池发出许多请求,线程池会尝试只用一个线程来服务所有请求。然而,如果你的应用程序发出请求的速度超过了线程池线程处理它们的速度,就会创建额外的线程。

 

任务取消(CancellationTokenSource)

一些情况下,后台任务可能运行很长时间,取消任务就非常有用了。.NET提供了一种标准的任务取消机制可用于基于任务的异步模式

取消基于CancellationTokenSource类,该类可用于发送取消请求。请求发送给引用CancellationToken类的任务,其中CancellationToken类与CancellationTokenSource类相关联。

使用示例如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //构造函数 指定延迟2秒后自动取消任务
 6             CancellationTokenSource source = new CancellationTokenSource(2000);
 7             //注册一个任务取消后执行的委托
 8             source.Token.Register(() =>
 9             {
10                 Console.WriteLine("线程Id:{0} 任务被取消后的业务逻辑正在运行", Thread.CurrentThread.ManagedThreadId);
11             });
12             //启动任务,将取消标记源带入参数
13             Task.Run(() =>
14             {
15                 while (!source.IsCancellationRequested)//IsCancellationRequested为True时取消任务
16                 {
17                     Thread.Sleep(100);
18                     Console.WriteLine("线程Id:{0} 任务正在运行", Thread.CurrentThread.ManagedThreadId);
19                 }
20             }, source.Token);
21             //主线程挂起2秒后手动取消任务
22             {
23                 //Thread.Sleep(2000);
24                 //source.Cancel();//手动取消任务
25             }
26             //主线程不阻塞,2秒后自动取消任务
27             {
28                 source.CancelAfter(2000);
29             }
30             Console.ReadKey();
31         }
32     }

执行结果:

图片 8

根据Register方法绑定任务取消后的委托

1   public CancellationTokenRegistration Register(Action callback);
2   public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext);
3   public CancellationTokenRegistration Register(Action<object> callback, object state);
4   public CancellationTokenRegistration Register(Action<object> callback, object state, bool useSynchronizationContext);

手动取消任务Cancel方法

自动取消任务

1.CancelAfter方法后面可以带入参数指定延迟多少后时间取消任务。

1   public void CancelAfter(TimeSpan delay);
2   public void CancelAfter(int millisecondsDelay);

2.CancellationTokenSource构造函数可以带入参数指定延迟多少时间后取消任务。

1   public CancellationTokenSource(TimeSpan delay);
2   public CancellationTokenSource(int millisecondsDelay);

任务绑定CancellationTokenSource对象,在Task源码中可以带入CancellationToken对象的启动任务方式都可以绑定CancellationTokenSource。

图片 9

 

13            Thread.Sleep(1000);//模拟代码操作   

    在任务执行过程中产生的未处理异常,任务会把它暂时隐藏起来,装进一个集合中。当我们调用Wait方法或者Result属性时,任务会抛出一个AggregateException异常。我们可以通过调用AggregateException对象的只读属性InnerExceptions来得到一个ReadOnlyCollection<Exception>对象,它才是存储抛出异常的集合,它的第一个元素就是最初抛出的异常。同样的,AggregateException对象的InnerException属性也会返回最初抛出的异常。

当一个线程池线程闲着没事一段时间之后,线程会自己醒来终止自己以释放资源。

3) 最终状态

异步等待 (Task.Delay)

 异步等待非常实用,因此它成为Task类的一个静态方法

 常用的使用方式有2种,如下:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //第1种
 6             {
 7                 Task.Delay(2000).ContinueWith((o) =>
 8                 {
 9                     Console.WriteLine("线程Id:{0},异步等待2秒后执行的逻辑", Thread.CurrentThread.ManagedThreadId);
10                 });
11             }
12             //第2种
13             {
14                 Task.Delay(3000).GetAwaiter().OnCompleted(() =>
15                 {
16                     Console.WriteLine("线程Id:{0},异步等待3秒后执行的逻辑", Thread.CurrentThread.ManagedThreadId);
17                 });
18             }
19             Console.WriteLine("主线程Id:{0},继续执行", Thread.CurrentThread.ManagedThreadId);
20             Console.ReadKey();
21         }
22     }

执行结果如下:

图片 10

Task.DelayThread.Sleep的异步版本。而它们的区别如下(引自 禅道 ):

1.Thread.Sleep 是同步延迟,Task.Delay异步延迟。

2.Thread.Sleep 会阻塞线程,Task.Delay不会。

3.Thread.Sleep不能取消,Task.Delay可以。

4. Task.Delay() 比 Thread.Sleep() 消耗更多的资源,但是Task.Delay()可用于为方法返回Task类型;或者根据CancellationToken取消标记动态取消等待。

5. Task.Delay() 实质创建一个运行给定时间的任务, Thread.Sleep() 使当前线程休眠给定时间。

 

 14        }

    值得重视的是,由于任务的隐藏机制的特点,一旦产生异常后,如果我们不调用相应的方法或者属性查看异常,我们也无法判断是否有异常产生(Task不会主动抛出异常)。当Task对象被GC回收时,Finalize方法会查检是否有未处理的异常,如果不幸刚才好有,则Finalize方法会将此AggregateException再度抛出,如果再不幸,我们没有捕获处理这个异常,则我们的程序会立即中止运行。如果发生这样的事情,会是多么大的灾难啊!

二、ThreadPool的简单使用练习

Task实例有三种可能的最终状态

异常(AggregateException)

与线程不同,任务可以随时抛出异常。所以,如果任务中的代码抛出一个未处理异常,那么这个异常会自动传递到调用Wait()或Task<TResult>的Result属性的代码上。
任务的异常将会自动捕获并抛给调用者。为确保报告所有的异常,CLR会将异常封装在AggregateException容器中,该容器公开的InnerExceptions属性中包含所有捕获的异常,从而更适合并行编程。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             try
 6             {
 7                 Task.Run(() =>
 8                 {
 9                     throw new Exception("错误");
10                 }).Wait();
11             }
12             catch (AggregateException axe)
13             {
14                 foreach (var item in axe.InnerExceptions)
15                 {
16                     Console.WriteLine(item.Message);
17                 }
18             }
19             Console.ReadKey();
20         }
21     }

上述示例控制台会显示:错误

注意

使用TaskIsFaultedIsCanceled属性,就可以不重新抛出异常而检测出错的任务。
1.IsFaulted和IsCanceled都返回False,表示没有错误发生。
2.IsCanceled为True,则任务抛出了OperationCanceledOperation(取消线程正在执行的操作时在线程中抛出的异常)。
3.IsFaulted为True,则任务抛出另一种异常,而Exception属性包含了该错误。

15    }

    为了避免这种不幸的发生,我们可以通过注册TaskScheduler类的静态UnobservedTaskException事件来处理这种未被处理的异常,避免程序的崩溃。

图片 11图片 12

说明

TaskStatus.Canceled

该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的 CancellationToken 发出了信号。Task属性IsFaulted被设置为true

TaskStatus.Faulted

由于未处理异常的原因而完成的任务。Task属性IsCanceled被设置为true

TaskStatus.RunToCompletion

已成功完成执行的任务。Task属性IsCompleted被设置为true,IsFaulted和IsCanceled被设置为false

1.Flatten

当子任务抛出异常时,通过调用Flatten方法,可以消除任意层次的嵌套以简化异常处理。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var parent = Task.Factory.StartNew(() =>
 6             {
 7                 int[] numbers = { 0 };
 8                 var childFactory = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None);
 9                 childFactory.StartNew(() => 10 / numbers[0]);//除零
10                 childFactory.StartNew(() => numbers[1]);//超出索引范围
11                 childFactory.StartNew(() => throw null);//空引用
12             });
13             try
14             {
15                 parent.Wait();
16             }
17             catch (AggregateException axe)
18             {
19                 foreach (var item in axe.Flatten().InnerExceptions)
20                 {
21                     Console.WriteLine(item.Message);
22                 }
23             }
24             Console.ReadKey();
25         }
26     }

图片 13

嘿,你会发现结果是一样的。再来看看这个是什么:TaskCreationOptions这个类型是一个枚举类型,传递一些标志来控制Task的执行方式。TaskCreationOptions定义如下:慢点,注释很详细,看看这些有好处,TaskScheduler(任务调度器)不懂没关系,请继续往下看,我会介绍的,但请注意,这些标识都只是一些提议而已,在调度一个Task时,可能会、也可能不会采纳这些提议,不过有一条要注意:AttachedToParent标志,它总会得到Task采纳,因为它和TaskScheduler本身无关。

四、任务启动任务

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Main Thread,当前线程:{Thread.CurrentThread.ManagedThreadId}");
            ThreadPool.QueueUserWorkItem(Calculate,5);
            Console.WriteLine($"Main Thread doing other work,当前线程:{Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
            Console.WriteLine("hi <Enter> to end this program~~");
            Console.Read();
        }

        //这个方法的签名必须匹配waitcallback委托
        public static void Calculate(object state)
        {
            //这个方法由一个线程池线程执行
            Console.WriteLine($"In Calculate:state={state},当前线程:{Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
            //这个方法返回后,线程回到池中,等待另一个任务
        }
    }

 

2.Handle

 如果需要只捕获特定类型异常,并重抛其它类型的异常,Handle方法为此提供了一种快捷方式。

Handle接受一个predicate(异常断言),并在每个内部异常上运行此断言。

1 public void Handle(Func<Exception, bool> predicate);

如果断言返回True,它认为该异常是“已处理”,当所有异常过滤之后:

1.如果所有异常是已处理的,异常不会抛出。

2.如果存在异常未处理,就会构造一个新的AggregateException对象来包含这些异常并抛出。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var parent = Task.Factory.StartNew(() =>
 6             {
 7                 int[] numbers = { 0 };
 8                 var childFactory = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None);
 9                 childFactory.StartNew(() => 10 / numbers[0]);//除零
10                 childFactory.StartNew(() => numbers[1]);//超出索引范围
11                 childFactory.StartNew(() => throw null);//空引用
12             });
13             try
14             {
15                 try
16                 {
17                     parent.Wait();
18                 }
19                 catch (AggregateException axe)
20                 {
21                     axe.Flatten().Handle(ex =>
22                     {
23                         if (ex is DivideByZeroException)
24                         {
25                             Console.WriteLine("除零-错误处理完毕");
26                             return true;
27                         }
28                         if (ex is IndexOutOfRangeException)
29                         {
30                             Console.WriteLine("超出索引范围-错误处理完毕");
31                             return true;
32                         }
33                         return false;//所有其它 异常重新抛出
34                     });
35 
36                 }
37             }
38             catch (AggregateException axe)
39             {
40                 foreach (var item in axe.InnerExceptions)//捕获重新抛出的异常
41                 {
42                     Console.WriteLine(item.Message);
43                 }
44             }
45             Console.ReadKey();
46         }
47     }

执行结果:

图片 14

 

来看下这段代码:

    任务的强大与灵活之一是,当我们完成一个任务时,可以自动开始一个新任务的执行。如下所示:

View Code

3 创建并执行任务

 结语

1.async和await这两个关键字下篇记录。

2.任务调度器(TaskScheduler)是Task之所以如此灵活的本质,我们常说Task是在ThreadPool上更升级化的封装,其实很大程度上归功于这个对象,考虑下篇要不要说一下,但其实我看的都头疼...

3.Task类包含很多的重载,最好F12跳到Task内熟悉下结构。

 

1        static void Main(string[] args) 

using System; 
using System.Threading; 
using System.Threading.Tasks;

namespace TaskDemo 

    public class AutoTask 
    { 
        static void Main() 
        { 
            Task task = new Task(() => { Thread.Sleep(5000); Console.WriteLine("Hello,"); Thread.Sleep(5000); }); 
            task.Start(); 
            Task newTask = task.ContinueWith(t => Console.WriteLine("World!")); 
            Console.Read(); 
        } 
    } 
}

对于ContinueWith方法,我们可以配合TaskContinuationOptions枚举,得到更多我们想要的行为。

 

运行结果:

1)public Task StartNew(Action action)

参考文献 

CLR via C#(第4版) Jeffrey Richter

C#高级编程(第10版) C# 6 & .NET Core 1.0   Christian Nagel  

果壳中的C# C#Net多线程编程,中TASK类的使用。5.0权威指南  Joseph Albahari

C#并发编程 经典实例  Stephen Cleary

...

 

2        { 

五、子任务

图片 15

参数:

3            

    任务是支持父子关系的,即在一个任务中创建新任务。如下所示:

有时上图标注这两行输出结果顺序会颠倒,这是因为两个方法相互之间是异步运行的,windows调度器决定先调度哪一个线程。

  action:要异步执行的操作委托

  4            //1000000000这个数字会抛出System.AggregateException 

using System; 
using System.Threading.Tasks;

namespace TaskDemo 

    class ChildTask 
    { 
        static void Main() 
        { 
            Task parant = new Task(() => 
            { 
Net多线程编程,中TASK类的使用。                new Task(() => Console.WriteLine("Hello")).Start(); 
                new Task(() => Console.WriteLine(",")).Start(); 
                new Task(() => Console.WriteLine("World")).Start(); 
                new Task(() => Console.WriteLine("!")).Start(); 
            }); 
            parant.Start(); 
            Console.ReadLine(); 
        } 
    } 
}

值得注意的是,以上代码中所示的子任务的调用并不是以代码的出现先后为顺序来调用的。

三、执行上下文

返回值:

5  

六、任务工厂

每个线程都关联一个执行上下文数据结构。

  已启动的 System.Threading.Tasks.Task

6            Taskt = new Task(n => Sum((Int32)n), 1000000000); 

   在某些情况下,我们会遇到创建大量的任务,而恰好这些任务共用某个状态参数(如CancellationToken),为了避免大量的调用任务的构造器和一次又一次的参数传递,我们可以使用任务工厂来为我们处理这种大量创建工作。如下代码所示:

执行上下文(execution context)包括的东西有安全设置(压缩栈、Thread的Principal属性和Windows的身份)、宿主设置(System.Threading.HostExecutionContextManager)以及逻辑调用上下文数据(参见System.Runtime.Remoting.Messaging.CallContext的LogicalSetData和LogicalGetData方法)。

异常:

using System; 
using System.Threading; 
using System.Threading.Tasks;

默认情况下,CLR自动造成初始线程的执行上下文“流向”任何辅助线程。这造成将上下文信息传给辅助线程,但这会对性能造成一定影响。

  System.ArgumentNullException:当 action 参数为 null 时引发的异常。

 8            //可以现在开始,也可以以后开始  

namespace TaskDemo 

    public class FactoryOfTask 
    { 
        static void Main() 
        { 
            Task parent = new Task(() => 
            { 
                CancellationTokenSource cts = new CancellationTokenSource(); 
                TaskFactory tf = new TaskFactory(cts.Token); 
                var childTask = new[] 
                { 
                 tf.StartNew(()=>ConcreteTask(cts.Token)), 
                 tf.StartNew(()=>ConcreteTask(cts.Token)), 
                 tf.StartNew(()=>ConcreteTask(cts.Token)) 
                };

这是因为执行上下文中包含大量信息,而收集所有这些信息,再把它们复制到辅助线程,要耗费不少时间。

2)public static Task Run(Action action)

9 10            t.Start();

                Thread.Sleep(5000);//此处睡眠等任务开始一定时间后才取消任务 
                cts.Cancel(); 
            } 
            );

System.Threading.ExecutionContext类,允许你控制线程的执行上下文如何从一个线程“流向”另一个。可用这个类 阻止上下文流动以提升应用程序的性能。

参数:

11 12            //Wait显式的等待一个线程完成

            parent.Start();//开始执行任务 
            Console.Read(); 
        }

图片 16图片 17

  action:表示在线程池执行的队列的任务

13 14            t.Wait();

        static void ConcreteTask(CancellationToken token) 
        { 
            while (true) 
            { 
                if (!token.IsCancellationRequested) 
                { 
                    Thread.Sleep(500); 
                    Console.Write("."); 
                } 
                else 
                { 
                    Console.WriteLine("任务取消"); 
                    break; 
                } 
            } 
        } 
    } 
}

    class Program
    {
        static void Main(string[] args)
        {
            //将一些数据放到Main线程的逻辑调用上下文中
            CallContext.LogicalSetData("Name", "Michael");
            //初始化要由线程池线程做的一些工作
            //线程池线程能访问逻辑调用上下文结构
            ThreadPool.QueueUserWorkItem(
                state => Console.WriteLine($"Name={CallContext.LogicalGetData("Name")}"));
            //阻止Main线程的执行上下文的流动
            ExecutionContext.SuppressFlow();
            //初始化要由线程池做的工作
            //线程池线程不能访问逻辑调用上下文数据
            ThreadPool.QueueUserWorkItem(
                state => Console.WriteLine($"Name={CallContext.LogicalGetData("Name")}"));
            //恢复Main线程的执行上下文的流动,
            //以免将来使用更多的线程池线程
            ExecutionContext.RestoreFlow();

            Console.ReadLine();
        }
    }

返回值:

15            16            Console.WriteLine("The Sum is:" t.Result);

七、任务调度程序

View Code

  已启动的 System.Threading.Tasks.Task

17        }

    任务的调度通过调度程序来实现的,目前,.NET 4.0内置两种任务调度程序:线程池任务调度程序(thread pool task scheduler)和同步上下文任务调度程序(synchronization context task scheduler)。默认情况下,应用程序使用线程池任务调度程序调用线程池的工作线程来完成任务,如受计算限制的异步操作。同步上下文任务调度程序通常使用UI线程来完成与Windows Forms,Windows Presentation Foundation(WPF)以及SilverLight应用程序相关的任务。

编译后运行结果如下:

异常:

18 19        private static Int32 Sum(Int32 i)

   可喜的是,.NET 4.0 提供了TaskScheduler抽象类供开发人员继承来实现自定义任务调度程序的开发,有兴趣的同学可以试试。

图片 18

  System.ArgumentNullException:当 action 参数为 null 时引发的异常。

20        {

八、总结

四、协作式取消和超时,System.Threading.CancellationTokenSource的简单使用

3)public void Start()

21            Int32 sum = 0;

      任务给了我们更多的方便性、灵活性的同时,也带来了比线程池更多的资源消耗。如果想减少资源消耗,请直接使用线程池QueueUserWorkItem方法效果会更好;如果想要更多的控制与灵活性,任务(Task)是不二的选择。这个要我们开发者自己去斟酌了。

Microsoft.NET Framework提供了标准的取消操作模式。这个模式是协作式的,意味着要取消的操作必须显式支持取消。

启动 System.Threading.Tasks.Task,并将它安排到当前的 System.Threading.Tasks.TaskScheduler中执行。

22            for (; i > 0; i--)

     

CancellationToken实例是轻量级值类型,包含单个私有字段,即对其CancellationTokenSource对象的引用。

异常:

23                checked { sum = i; }

参考文献:《CLR Via C#》,Third edtion, 作者:Jeffrey Richer,726页-739页

在计算限制操作的循环中,可定时调用CancellationToken的IsCancellationRequsted属性,了解循环是否应该提前终止,从而终止计算限制的操作。

  System.ObjectDisposedException:已释放 System.Threading.Tasks.Task 实例。

24            return sum;

《Introducing .NET 4.0 With Visual Studio 2010》,作者:Alex Mackey,106页-111页

提前终止的好处在于,CPU不需要再把时间浪费在你对结果不感兴趣的操作上。

  System.InvalidOperationException:System.Threading.Tasks.Task 未处于有效状态,无法启动。 它可能已启动、已执行或已取消,或者可能已经不支持以直接计划的方式创建。

25        }

 

图片 19图片 20

注意:

26    }

    static void Main(string[] args)
        {
            Go();
        }

        public static void Go()
        {
            CancellationTokenSource token = new CancellationTokenSource();
            //将CancellationTokenSource和参数 传入操作
            ThreadPool.QueueUserWorkItem(
                o => Count(token,1000));
            Console.WriteLine($"Hit <Enter> to cancel operation");
            Console.ReadLine();
            token.Cancel();//如果Count方法已返回,Cancel没有任何效果
            //执行cancel后 立即返回,方法从这里继续运行
            Console.ReadLine();
        }
        public static void Count(CancellationTokenSource token,Int32 counto)
        {
            for (int count = 0; count < counto; count  )
            {
                if(token.IsCancellationRequested)
                {
                    Console.WriteLine("操作被取消");
                    break;
                }
                Console.WriteLine(count);
                Thread.Sleep(200); //出于显示目的而浪费一些时间你
            }
            Console.WriteLine("Count is done");
        }

  仅使用Task的构造器来创建Task的实例并不能启动任务,还要使用Start才能启动任务。

 这段代码大家应该猜得出是什么意思吧,人人都会写。  但是,我的结果为什么是t.Result而不直接是返回的Sum呢?  有没有多此一举的感觉?下面我来说说这段代码我想表达的意思:  在一个线程调用Wait方法时,系统会检查线程要等待的Task是否已经开始执行,如果任务正在执行,那么这个Wait方法会使线程阻塞,知道Task运行结束为止。  就说上面的程序执行,因为累加数字太大,它抛出算术运算溢出错误,在一个计算限制任务抛出一个未处理的异常时,这个异常会被“包含”不并存储到一个集合中,而线程池线程是允许返回到线程池中的,在调用Wait方法或者Result属性时,这个成员会抛出一个System.AggregateException对象。  现在你会问,为什么要调用Wait或者Result?或者一直不查询Task的Exception属性?你的代码就永远注意不到这个异常的发生,如果不能捕捉到这个异常,垃圾回收时,抛出AggregateException,进程就会立即终止,这就是“牵一发动全身”,莫名其妙程序就自己关掉了,谁也不知道这是什么情况。所以,必须调用前面提到的某个成员,确保代码注意到异常,并从异常中恢复。悄悄告诉你,其实在用Result的时候,内部会调用Wait。  怎么恢复?  为了帮助你检测没有注意到的异常,可以向TaskScheduler的静态UnobservedTaskException时间等级一个回调方法,当Task被垃圾回收时,如果出现一个没有被注意到的异常,CLR终结器会引发这个事件。一旦引发,就会向你的时间处理器方法传递一个UnobservedTaskExceptionEvenArgs对象,其中包含了你没有注意的AggregateException。然后再调用UnobservedTasExceptionEvenArgs的SetObserved方法来指出你的异常已经处理好了,从而阻止CLR终止进程。这是个图省事的做法,要少做这些,宁愿终止进程,也不要呆着已经损坏的状态而继续运行。做人也一样,病了宁肯休息,也不要带病坚持上班,你没那么伟大,公司也不需要你的这一点伟大,命是自己的。(─.─|||扯远了。  除了单个等待任务,Task 还提供了两个静态方法:WaitAny和WaitAll,他们允许线程等待一个Task对象数组。  WaitAny方法会阻塞调用线程,知道数组中的任何一个Task对象完成,这个方法会返回一个索引值,指明完成的是哪一个Task对象。如果发生超时,方法将返回-1。它可以通过一个CancellationToken取消,会抛出一个OperationCanceledException。  WaitAll方法也会阻塞调用线程,知道数组中的所有Task对象都完成,如果全部完成就返回true,如果超时就返回false。当然它也能取消,同样会抛出OperationCanceledException。  说了这么两个取消任务的方法,现在来试试这个方法,加深下印象,修改先前例子代码,完整代码如下:

View Code

4)Task.Factory.StartNew与Task.Run

 1        static void Main(string[] args) 

运行结果如下图所示:

Task.Factory.StartNew重载方法提供更多的参数,可以控制如何计划执行任务以及如何向调试器公开计划任务的机制和控制任务的创建和执行的可选行为。

2        { 

图片 21

而Task.Run提供的方法则不具有上述控制机制。

3            CancellationTokenSource cts = new CancellationTokenSource();

可调用CancellationTokenSource的Register方法登记一个或多个在取消一个CancellationTokenSource时调用的方法。

 

 4            

向被取消的CancellationTokenSource登记一个回调方法,将由调用Register的线程调用回调方法(如果为useSynchronizationContext参数传递了true值,就可能要通过调用线程的SynchronizationContext进行)。

4 等待任务完成

  5              

多次调用Register,多个调用方法都会调用。这些回调方法可能抛出未处理的异常。

1)public void Wait()

6  7            Taskt = new Task(() => Sum(cts.Token,10000), cts.Token); 

如果调用CancellationTokenSource的Cancel方法,向它传递true,那么抛出了未处理异常的第一个回调方法会阻止其他回调方法的执行,抛出的异常也会从Cancel中抛出。

等待 System.Threading.Tasks.Task 完成执行过程

8  9            //可以现在开始,也可以以后开始 

如果调用Cancel并向它传递false,那么登记的所有回调方法都会调用。所有未处理的异常都会添加到一个集合中。所有回调方法都执行好后,其中任何一个抛出了未处理的异常,Cancel就会抛出一个AggregateException,该异常实例的InnerExceptions属性被设为已抛出的所有异常对象的集合。

异常:

10            11            t.Start();

图片 22图片 23

  ObjectDisposedException:Task 对象已被释放。

12 13            //在之后的某个时间,取消CancellationTokenSource 以取消Task

        static void Main(string[] args)
        {
            var cts1 = new CancellationTokenSource();
            cts1.Token.Register(() => Console.WriteLine($"cts1被取消"));
            var cts2 = new CancellationTokenSource();
            cts2.Token.Register(() => Console.WriteLine($"cts2被取消"));
            var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
            linkedCts.Token.Register(() => Console.WriteLine($"linkedCts 被取消"));
            cts2.Cancel();
            Console.WriteLine($"cts1 canceled={cts1.IsCancellationRequested},cts2 canceled={cts2.IsCancellationRequested},"  
                $"linkedCts={linkedCts.IsCancellationRequested}");
            Console.ReadLine();
    }    

  AggregateException:System.Threading.Tasks.Task 已取消或在 System.Threading.Tasks.Task 的执行期间引发了异常。如果任务已被取消,System.AggregateException将包含其 System.AggregateException.InnerExceptions 集合中的 System.OperationCanceledException。

14 15            cts.Cancel();//这是个异步请求,Task可能已经完成了。我是双核机器,Task没有完成过

View Code

2)public static void WaitAll(params Task[] tasks)

16 17 18            //注释这个为了测试抛出的异常

运行结果如下图:

参数:

19            //Console.WriteLine("This sum is:" t.Result);

图片 24

  tasks:要等待的 Task 实例的数组

20            try

如果要在过一段时间后取消操作,要么用接收延时参数的构造器构造一个CancellationTokenSource对象,要么调用CancellationTokenSource的CancelAfter方法。

异常:

21            {

五、任务 

  ObjectDisposedException:一个或多个 Task 中的对象 tasks 已被释放。

22                //如果任务已经取消了,Result会抛出AggregateException

通过观察,我们发现 ThreadPool最大的问题是没有内建的机制让你知道 操作在什么时候完成,以及操作完成时获得返回值。鉴于此,Microsoft引入了任务的概念。

  ArgumentNullException:tasks 参数为 null或tasks 参数包含 null 元素。

23 24                Console.WriteLine("This sum is:" t.Result);

下面展示一个使用task的简单例子:

  AggregateException:在至少一个 Task 实例已取消。如果任务已被取消, AggregateException 异常包含 OperationCanceledException 中的异常其   AggregateException.InnerExceptions 集合。或在至少一个执行期间引发了异常 Task 实例。

25            }

图片 25图片 26

说明:

26            catch (AggregateException x)

        static void Main(string[] args)
        {
            Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
            //创建一个Task,现在还没有开始运行
            Task<Int32> t = new Task<int>(n => Sum((Int32)n), 10000);
            //可以后等待任务
            t.Start();
            //可选择显示等待任务完成
            t.Wait();
            //可获得结果(result属性内部会调用Wait)
            Console.WriteLine($"the Sum is:{t.Result},当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
            Console.ReadLine();
        }    
         private static Int32 Sum(Int32 n)
        {
            Int32 sum = 0;
            for (; n>0; n--)checked
            {
                sum  = n; //如果n太大,会抛出System.OverflowException
            }
            Console.WriteLine($"In Sum,当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
            return sum;
        }

  主线程会等待作为参数传入的任务tasks执行结束才会执行下一条语句。

27            {

View Code

3)public static int WaitAny(params Task[] tasks)

28                //将任何OperationCanceledException对象都视为已处理。

运行结果如右图:图片 27

参数:

29                //其他任何异常都造成抛出一个AggregateException,其中

如果计算限制任务抛出未处理的异常,异常会被“吞噬”并存储到一个集合中,调用wait方法或Result属性时,这些成员会抛出一个System.AggregateException对象。

  tasks:要等待的 Task 实例的数组

30                //只包含未处理的异常

AggregateException提供了一个Handle方法,它为AggregateException中包含的每个异常都调用一个回调方法。回调方法可以为每个异常决定如何对其处理;回调返回true表示异常已处理;返回false表示未处理。调用Handle后,如果至少有一个异常没有处理,就创建一个新的AggregateException对象,其中只包含未处理的异常。

异常:

31 32                x.Handle(e => e is OperationCanceledException);

Task的静态WaitAny方法会阻塞调用线程,直到数组中的任何Task对象完成。方法返回Int32数组索引值,指明完成的是哪个Task的对象

  System.ObjectDisposedException:System.Threading.Tasks.Task 已被释放。

33                Console.WriteLine("Sum was Canceled");

Task的静态WaitAll方法也会阻塞调用线程,直到数组中的所有Task对象完成。

  System.ArgumentNullException:tasks 参数为 null。

34            }

下面演示下task取消操作和task的异常处理

  System.ArgumentException:tasks 参数包含 null 元素。

35          36        }

图片 28图片 29

 

37 38        private static Int32 Sum(CancellationToken ct ,Int32 i)

         static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            Task<Int32> t = Task.Run(() => Sum(cts.Token, 10000), cts.Token);

            cts.Cancel(); 
            try
            {
                Console.WriteLine($"the Sum is:{t.Result},当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
            }
            catch (AggregateException ex)
            {
                //将任何OperationCanceledException对象都是为已处理
                //其他任何异常都造成抛出一个新的AggregateException
                //其中只包含未处理异常
                ex.Handle(e => e is OperationCanceledException);
                Console.WriteLine("Sum was canceled");
            }
            Console.ReadLine();
        }        
         private static Int32 Sum(CancellationToken ct, Int32 n)
        {
            Int32 sum = 0;
            for (; n>0; n--)checked
            {
                //再取消标志引用的CancellationTokenSource上调用Cancel,
                //下面这行代码就会抛出OperationCanceledException
                ct.ThrowIfCancellationRequested();
                sum  = n; //如果n太大,会抛出System.OverflowException
            }
            Console.WriteLine($"In Sum,当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
            return sum;
        }

5 取消任务

39        {

View Code

使用System.Threading.CancellationToken和System.Threading.CancellationTokenSource中断Task的执行。

40            Int32 sum = 0;

调用Wait,或者在任务尚未完成时查询任务的Result属性,极有可能造成线程池创建新线程,这增大了资源的消耗,也不利于性能和伸缩性。

1)System.Threading.CancellationToken

41            for (; i > 0; i--)

要知道一个任务在什么时候结束,任务完成时可启动另一个任务。

传播有关应取消操作的通知

42            {

Microsoft为我们提供了ContinueWith,下面简单展示使用

属性:

43                //在取消标志引用的CancellationTokenSource上如果调用

图片 30图片 31

  public bool IsCancellationRequested { get; }

44                //Cancel,下面这一行就会抛出OperationCanceledException

        static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            Task<Int32> t = Task.Run(() => Sum(cts.Token, 10000), cts.Token);
            Task cwt= t.ContinueWith(task => Console.WriteLine($"Sum result is {task.Result}"));
        }        
         private static Int32 Sum(CancellationToken ct, Int32 n)
        {
            Int32 sum = 0;
            for (; n>0; n--)checked
            {
                //再取消标志引用的CancellationTokenSource上调用Cancel,
                //下面这行代码就会抛出OperationCanceledException
                ct.ThrowIfCancellationRequested();
                sum  = n; //如果n太大,会抛出System.OverflowException
            }
            Console.WriteLine($"In Sum,当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
            return sum;
        }

方法:

45 46                ct.ThrowIfCancellationRequested();

View Code

  public void ThrowIfCancellationRequested();

47 48                checked { sum = i; }

Task对象内部包含了ContinueWith任务的一个集合。可在调用ContinueWith时传递对一组TaskContinuationOptions枚举值进行判断满足什么情况才执行ContinueWith。

  如果已请求取消此标记,则引发 System.OperationCanceledException。

49            }

图片 32

异常:

50            51            return sum;

偷个懒,哈哈。。。

  System.OperationCanceledException:该标记已请求取消。

52        }

任务可以启动多个子任务,下面简单展示下使用

      System.ObjectDisposedException:关联的System.Threading.CancellationTokenSource已被释放。

53    }

图片 33图片 34

2) System.Threading.CancellationTokenSource

  这个例子展示了一个任务在进行的时候中途取消的操作,我觉得它很有趣,你试试也会发现。  Lamada表达式写这个,是个亮点,得学学,将CancellationToken闭包变量“传递”。

        static void Main(string[] args)
        {
            Task<Int32[]> task = new Task<Int32[]>(() =>
            {
                var results = new Int32[3];
                new Task(() => results[0] = Sum(1000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => results[1] = Sum(1000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => results[2] = Sum(1000), TaskCreationOptions.AttachedToParent).Start();
                return results;
            });
            var cwt = task.ContinueWith(
                parentTask => Array.ForEach(parentTask.Result, Console.WriteLine));
            task.Start();
            Console.ReadLine();    
        }
        private static Int32 Sum( Int32 n)
        {
            Int32 sum = 0;
            for (; n>0; n--)checked
            {
                sum  = n; //如果n太大,会抛出System.OverflowException
            }
            Console.WriteLine($"In Sum,当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
            return sum;
        }    

通知 System.Threading.CancellationToken,告知其应被取消

 如果不用Lamada表达式,这问题还真不好解决:  Taskt = new Task(() => Sum(cts.Token,10000), cts.Token);  Sum(cts.Token,10000) 内的Token需要和cts.Token关联起来,你还能想出怎么关联起来么?

View Code

属性:

  好,任务取消也讲玩了,来看个更好用的技术:

TaskCreationOptions.AttachedToParrent标志将一个Task和创建它的Task关联,结果是除非所有子任务(以及子任务的子任务)结束运行,否则创建任务(父任务)不认为已经结束。

  public CancellationToken Token { get; }:获取与此 System.Threading.CancellationTokenSource 关联的 System.Threading.CancellationToken。

1        static void Main(string[] args) 

在一个Task对象的存在期间,可查询Task的只读Status属性了解它在其生存期的什么位置。

异常:

2        { 

 

  System.ObjectDisposedException:已释放标记源。

3  4            Taskt = new Task(i => Sum((Int32)i),10000); 

要创建一组共享相同配置的Task对象。可创建一个任务工厂来封装通用的配置。即TaskFactory。

方法:

5  6            //可以现在开始,也可以以后开始  

在调用TaskFactory或TaskFactory<TResult>的静态ContinueWhenAll和ContinueWhenAny方法,无论前置任务是如何完成的,ContinueWhenAll和ContinueWhenAny都会执行延续任务。

  public void Cancel():传达取消请求。

7              8            t.Start(); 

六、任务调度器

异常:

9 10            Task cwt =  t.ContinueWith(task=>Console.WriteLine("The sum is:{0}",task.Result));

对于不了解任务调度的小白来讲,可能遇到过下面这个场景

  System.ObjectDisposedException:此 System.Threading.CancellationTokenSource 已被释放。

11            cwt.Wait();

图片 35

  System.AggregateException:聚合异常包含由相关联的 System.Threading.CancellationToken 上已注册的回调引发的所有异常。

12            13        }

啊,怎么会这样呢?为什么不能在线程里更新UI组件。

 

14 15        private static Int32 Sum(Int32 i)

TaskScheduler对象负责执行被调度的任务,同时向Visual Studio调试器公开任务信息。

6 任务的返回值

16        {

FCL提供了两个派生自TaskScheduler的类型:线程池任务调度器(thread pool task scheduler),和同步上下文任务调度器(synchronization context task scheduler)。

1)Task类型

17            Int32 sum = 0;

默认情况下,所有应用程序使用的都是线程池任务调度器。可查询TaskScheduler的静态Default属性来获得对默认任务调度器的引用。

在第1节中已经介绍了Task。

18            for (; i > 0; i--)

同步上下文任务调度器适合提供了图形用户界面的应用程序。它将所有任务都调度给应用程序的GUI线程,使所有任务代码都能成功的更新UI组件。该调度不使用线程池。可执行TaskScheduler的静态FromCurrentSynchronizationContext方法来获得对同步上下文任务调度器的引用。

2)Task<TResult>类型

19            {

下面展示一个简单的例子,演示如何使用同步上下文任务调度器

属性

20                checked { sum = i; }

图片 36图片 37

定义

说明

public static TaskFactory<TResult> Factory { get; }

提供对用于创建 System.Threading.Tasks.Task<TResult> 实例的工厂方法的访问。

public TResult Result { get; }

获取此 System.Threading.Tasks.Task<TResult> 的结果值

21            }

     public partial class MainForm : Form
    {
        private readonly TaskScheduler m_syncContextTaskScheduler;
        public MainForm()
        {
            //获得一个对同步上下文任务调度器的引用
            m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            Text = "Synchronization Context Task Scheduler Demo";
            Visible = true; Width = 400; Height = 400;
        }

        private CancellationTokenSource m_cts;

        protected override void OnMouseClick(MouseEventArgs e)
        {
            if(m_cts!=null)
            {
                m_cts.Cancel(); //一个操作正在运行,取消它
                m_cts = null;
            }
            else
            {
                //任务没有开始启动它
                Text = "Operation running"; 
                m_cts = new CancellationTokenSource();
                //这个任务使用默认任务调度器,在一个线程池线程上运行
                Task<Int32> t = Task.Run(()=>Sum(1000),m_cts.Token);
                //这些任务使用 同步上下文任务调度器,在GUI线程上执行
                t.ContinueWith(task => Text = "Result:"   t.Result,
                    CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,
                    m_syncContextTaskScheduler);
                t.ContinueWith(task => Text = "Operation canceled ",
                    CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled,
                    m_syncContextTaskScheduler);
                t.ContinueWith(task => Text = "Operation defaulted ",
                    CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted,
                    m_syncContextTaskScheduler);
            }


            base.OnMouseClick(e);
        }
        private static Int32 Sum(Int32 n)
        {
            Int32 sum = 0;
            for (; n > 0; n--) checked
                {
                    sum  = n; //如果n太大,会抛出System.OverflowException
                }
            return sum;
        }
    }

方法

22            23            return sum;

View Code

  public Task ContinueWith(Action<Task<TResult>> continuationAction)

24        }

单击窗体的客户区域,就会在线程池线程上启动一个计算限制操作。使用线程池线程,因为GUI线程在此期间不会被阻塞,能响应其他UI操作。

参数:

25    }

 

  continuationAction:在 System.Threading.Tasks.Task<TResult> 完成时要运行的操作。在运行时,委托将作为一个参数传递给完成的任务。

ContinueWith?  啥东西~~??  要写可伸缩的软件,一定不能使你的线程阻塞。这意味着如果调用Wait或者在任务未完成时查询Result属性,极有可能造成线程池创建一个新线程,这增大了资源的消耗,并损害了伸缩性。  ContinueWith便是一个更好的方式,一个任务完成时它可以启动另一个任务。上面的例子不会阻塞任何线程。

Parallel就留在下篇来介绍吧。。。

异常:

 当Sum的任务完成时,这个任务会启动另一个任务以显示结果。ContinueWith会返回对新的Task对象的一个引用,所以为了看到结果,我需要调用一下Wait方法,当然你也可以查询下Result,或者继续ContinueWith,返回的这个对象可以忽略,它仅仅是一个变量。  还要指出的是,Task对象内部包含了ContinueWith任务的一个集合。所以,实际上可以用一个Task对象来多次调用ContinueWith。任务完成时,所有ContinueWith任务都会进入线程池队列中,在构造ContinueWith的时候我们可以看到一个TaskContinuationOptions枚举值,不能忽视,看看它的定义:PrefereFairness是尽量公平的意思,就是较早调度的任务可能较早的运行,先来后到,将线程放到全局队列,便可以实现这个效果。ExecuteSynchronously指同步执行,强制两个任务用同一个线程一前一后运行,然后就同步运行了。 看得是不是晕乎乎 ?有这么多枚举例子,怎么掌握啊?多看几次,知道任务的使用情况,以后用起来得心应手~想学新技术,就要能耐住,才能基础牢固。来看个例子,用用这些枚举。

天道酬勤,大道至简,坚持。

  System.ObjectDisposedException:System.Threading.Tasks.Task<TResult> 已被释放。

 1        static void Main(string[] args) 

  System.ArgumentNullException:continuationAction 参数为 null。

2        { 

注意:

3            Taskt = new Task(i => Sum((Int32)i),10000); 

  • 该方法的重载方法提供了更多的控制机制。可以传入CancellationToken、TaskContinuationOptions、TaskScheduler参数。
  • 使用Task.Factory.StartNew方法,如果传入的委托无返回值,那么方法执行的返回结果类型其实是Task<TResult>,通过Task<TResult>类型的Result 属性可以查看返回结果。对于串联的多个任务,若后续的任务要使用上一个任务的结果,那么Task.Factory.StartNew返回值类型必须是Task<TResult>或var。
  • 返回值可以是自定义类型。

4  5            t.Start(); 

 

6  7            t.ContinueWith(task=>Console.WriteLine("The sum is:{0}",task.Result), 8                TaskContinuationOptions.OnlyOnRanToCompletion); 

7 TaskCreationOptions (枚举类型)

9            10            t.ContinueWith(task=>Console.WriteLine("Sum throw:" task.Exception),11                TaskContinuationOptions.OnlyOnFaulted);

用途:控制任务创建与执行的行为。

12            13            t.ContinueWith(task=>Console.WriteLine("Sum was cancel:" task.IsCanceled),14                TaskContinuationOptions.OnlyOnCanceled);

说明

TaskCreationOptions.None

指定应使用默认行为

TaskCreationOptions.PreferFairness

提示 System.Threading.Tasks.TaskScheduler 以一种尽可 能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行

TaskCreationOptions.LongRunning

指定某个任务将是运行时间长、粗粒度的操作。 它会向 System.Threading.Tasks.TaskScheduler 提示,过度订阅可能是合理的。

TaskCreationOptions.AttachedToParent

指定将任务附加到任务层次结构中的某个父级

TaskCreationOptions.DenyChildAttach

如果尝试附有子任务到创建的任务,指定 System.InvalidOperationException 将被引发

TaskCreationOptions.HideScheduler

防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default当前计划程序

15            try

8 任务计划TaskScheduler

16            {

功能:扩展任务执行计划,例如自定义任务计划程序来实现性能加速。

17                t.Wait();  // 测试用

属性:

18            }

名称

说明

Current

当前正在执行的任务关联的 TaskScheduler

Id

TaskScheduler 的唯一 ID

MaximumConcurrencyLevel

指示此 TaskScheduler 能够支持的最大并发级别

19            catch (AggregateException)

 

20            {

9 串联多个任务

21                Console.WriteLine("出错");

1)public Task ContinueWith(Action<Task> continuationAction);

22            }

参数:

23            24            25        }

  continuationAction:在 System.Threading.Tasks.Task 完成时要运行的操作。 在运行时,委托将作为一个参数传递给完成的任务。

26 27        private static Int32 Sum(Int32 i)

异常:

28        {

  System.ObjectDisposedException:创建了 cancellationToken 的 System.Threading.CancellationTokenSource 已经被释放。

29            Int32 sum = 0;

  System.ArgumentNullException:continuationAction 参数为 null。

30            for (; i > 0; i--)

2)public Task ContinueWith(Action<Task> continuationAction,TaskContinuationOptions continuationOptions);

31            {

参数:

32                checked { sum = i; }

  continuationAction:根据在 continuationOptions 中指定的条件运行的操作。 在运行时,委托将作为一个参数传递给完成的任务。

33            }

  continuationOptions:用于设置计划延续任务的时间以及延续任务的工作方式的选项。

34            35            return sum;

3)TaskContinuationOptions

36        }

enum类型,用于设置计划延续任务的时间以及延续任务的工作方式的选项。 这包括条件(如 System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled)和执行选项(如

37    }

System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously)。

  ContinueWith讲完了。可是还没有结束哦。  AttachedToParnt枚举类型(父任务)也不能放过!看看怎么用,写法有点新奇,看看: 

说明

ContinuationOptions.None

指定应使用默认行为。默认情况下,完成前面的任务之后将安排运行延续任务,而不考虑前面任务的最终 System.Threading.Tasks.TaskStatus。

 

ContinuationOptions.LongRunning

指定某个任务将是运行时间长、粗粒度的操作。 它会向 System.Threading.Tasks.TaskScheduler 提示,过度订阅可能是合理的。

ContinuationOptions.AttachedToParent

指定将任务附加到任务层次结构中的某个父级。

ContinuationOptions.DenyChildAttach

如果尝试附有子任务到创建的任务,指定 System.InvalidOperationException 将被引发。

ContinuationOptions.HideScheduler

防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为System.Threading.Tasks.TaskScheduler.Default当前计划程序。

ContinuationOptions.LazyCancellation

在延续取消的情况下,防止延续的完成直到完成先前的任务

ContinuationOptions.NotOnRanToCompletion

指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。 此选项对多任务延续无效

ContinuationOptions.NotOnFaulted

指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。 此选项对多任务延续无效

ContinuationOptions.OnlyOnCanceled

指定只应在延续任务前面的任务已取消的情况下才安排延续任务。 此选项对多任务延续无效

ContinuationOptions.NotOnCanceled

指定不应在延续任务前面的任务已取消的情况下安排延续任务。 此选项对多任务延续无效

ContinuationOptions.OnlyOnFaulted

指定只应在延续任务前面的任务引发了未处理异常的情况下才安排延续任务。 此选项对多任务延续无效

ContinuationOptions.OnlyOnRanToCompletion

指定只应在延续任务前面的任务已完成运行的情况下才安排延续任务。 此选项对多任务延续无效

ContinuationOptions.ExecuteSynchronously

指定应同步执行延续任务。 指定此选项后,延续任务将在导致前面的任务转换为其最终状态的相同线程上运行。 如果在创建延续任务时已经完成前面的任务,则延续任务将在创建此延续任务的线程上运行。只应同步执行运行时间非常短的延续任务

ContinuationOptions.PreferFairness

提示 System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。

 1        static void Main(string[] args) 

 

2        { 

注意:

3            Taskparent = new Task(() => {

1)可以通过位操作组合使用多个值。

 4                var results = new Int32[3]; 

2)使用ContinuationOptions.None意味着不论前面的任务是否被取消,延续任务都会执行。

5                //

异常:

 6                new Task(() => results[0] = Sum(10000), TaskCreationOptions.AttachedToParent).Start(); 

  System.ObjectDisposedException:System.Threading.Tasks.Task 已被释放。

7                new Task(() => results[1] = Sum(20000), TaskCreationOptions.AttachedToParent).Start(); 

  System.ArgumentNullException:continuationAction 参数为 null。

8                new Task(() => results[2] = Sum(30000), TaskCreationOptions.AttachedToParent).Start(); 

  System.ArgumentOutOfRangeException:continuationOptions 参数为 System.Threading.Tasks.TaskContinuationOptions 指定无效值。

9                return results;

3)子任务(嵌套任务):在父任务的委托中创建的 System.Threading.Tasks.Task 实例。 子任务包括两种:附加的子任务与分离的子任务

10            });

  • 分离的子任务是不依赖于其父级而执行。
  • 附加的子任务是使用 TaskCreationOptions.AttachedToParent 选项创建的,依赖于其父任务而执行。 对父任务使用TaskCreationOptions.DenyChildAttach来阻止子任务附加到父任务。
  • 一个任务可以创建任意数量的附加的子任务和分离的子任务,这仅受系统资源限制。
  • 不提倡创建附加的子任务,这样会大大增加程序设计的复杂性。

11 12            var cwt = parent.ContinueWith( parentTask=>Array.ForEach(parentTask.Result,Console.WriteLine));

 

13                    14 15            parent.Start();

10 使用模式

16            cwt.Wait();

1)创建任务

17        }

基本形式:

18 19        private static Int32 Sum(Int32 i)

 1 private void CreatTask()
 2 {
 3     //创建并执行任务
 4     Task task = new Task(() =>
 5     {
 6         //具体操作
 7     });
 8     task.Start();
 9 
10     //创建并将任务加入执行计划,使用StartNew
11     Task.Factory.StartNew(() => {
12         //具体操作
13     });
14 
15     //创建并将任务加入执行计划,使用Run
16     Task.Run(() =>
17     {
18         //具体操作
19     });
20 
21     //安排任务
22     Task.Factory.StartNew(() =>
23     {
24         //具体操作
25     },TaskCreationOptions.PreferFairness);
26 }    

20        {

创建附加的子任务:

21            Int32 sum = 0;

 1 private void CreateTask_Parent()
 2 {
 3     //附加子任务
 4     var taskParent = Task.Factory.StartNew(() =>
 5     {
 6         //操作......
 7         var child = Task.Factory.StartNew(() =>
 8         {
 9             //具体操作
10         }, TaskCreationOptions.AttachedToParent);
11     });
12     taskParent.Wait();
13 
14 
15     //阻止附加子任务
16     var taskParentZ = Task.Factory.StartNew(() =>
17     {
18         //操作......
19         var child = Task.Factory.StartNew(() =>
20         {
21             //即使设置TaskCreationOptions.AttachedToParent也无法将其附加到父任务
22             //具体操作
23         }, TaskCreationOptions.AttachedToParent);
24     }, TaskCreationOptions.DenyChildAttach);
25     taskParentZ .Wait();
26 }    

22            for (; i > 0; i--)

2)取消任务

23            {

 1 public static void CancelFromExternal_Task()
 2 {
 3     CancellationTokenSource cts = new CancellationTokenSource();
 4 
 5     //其他操作...
 6 
 7     //计算condition
 8     bool condition = ...;
 9     if (condition) cts.Cancel();
10     //或使用Operation2_Task(cts);
11     Operation1_Task(cts);
12     //其他操作...
13 
14 }
15 
16     //1 使用IsCancellationRequested属性
17     private static void Operation1_Task(CancellationTokenSource cts)
18     {
19         CancellationToken ct = cts.Token;
20         Task.Factory.StartNew(() => 
21         {
22             //其他操作...
23             //return只对当前子线程有效
24             if (ct.IsCancellationRequested)
25             { return; }
26             //其他操作...
27         },ct);     
28     }
29 
30     //2 使用抛异常的方式
31     private static void Operation2_Task(CancellationTokenSource     cts)
32     {
33         CancellationToken ct = cts.Token; 
34         Task.Factory.StartNew(() =>
35         {
36             //其他操作...
37             ct.ThrowIfCancellationRequested();
38             //其他操作...
39         }, ct);
40     }    

24                checked { sum = i; }

3)等待任务完成

25            }

 1 private void WaitFunc()
 2 {
 3     Task task = new Task(() => 
 4     {
 5         //具体操作
 6     });
 7     task.Start();
 8     task.Wait();
 9 }
10 
11 private void WaitAllFunc()
12 {
13     Task task1 = Task.Run(() =>
14     {
15         //具体操作 
16     });
17     Task task2 = Task.Run(() =>
18     {
19         //具体操作 
20     });
21     //等待task1与task2,直到它们完成为止
22     Task.WaitAll(task1, task2);
23 
24     //等待task1与task2,如果超过1000毫秒则返回。
25     Task.WaitAll(new Task[] { task1, task2 },1000);
26 }

26            return sum;

4)串联多个任务

27        }

 1 private void contactTasks()
 2 {
 3     var t1 = Task.Factory.StartNew(() =>
 4     {
 5         //具体操作1
 6         //return 返回值;
 7     });
 8 
 9     var t2 = t1.ContinueWith((t) =>
10     {
11         //具体操作2
12         //return 返回值;
13     });
14 
15     var t3 = t2.ContinueWith((t) =>
16     {
17         //具体操作3
18     });
19 
20     var t4 = t1.ContinueWith((t) =>
21     {
22         //具体操作4
23     });
24 
25     var t5 = t1.ContinueWith((t) =>
26     {
27         //具体操作5
28     });
29 
30     Task.WaitAll(t3, t4, t5);
31 }

28    }复制代码Oh,我都写晕了。。。( ﹏ )~例子中,父任务创建兵启动3个Task对象。默认情况下,一个任务创建的Task对象是顶级任务,这些任务跟创建它们的那个任务没有关系。TaskCreationOptions.AttachedToParent标志将一个Task和创建它的那个Task关联起来,除非所有子任务(子任务的子任务)结束运行,否则创建任务(父任务)不会认为已经结束。调用ContinueWith方法创建一个Task时,可以指定TaskContinuationOptions.AttachedToParent标志将延续任务置顶为一个子任务。


 看了这么多任务的方法操作示例了,现在来挖挖任务内部构造:  每个Task对象都有一组构成任务状态的字段。  一个Int32 ID(只读属性)代表Task执行状态的一个Int32对父任务的一个引用对Task创建时置顶TaskSchedule的一个引用对回调方法的一个引用对要传给回调方法的对象的一个引用(通过Task只读AsyncState属性查询)对一个ExceptionContext的引用对一个ManualResetEventSlim对象的引用还有没个Task对象都有对根据需要创建的一些补充状态的一个引用,补充状态包含这些:一个CancellationToken一个ContinueWithTask对象集合为抛出未处理异常的子任务,所准备的一个Task对象集合说了这么多,只想要大家知道:

转载与引用请注明出处。

虽然任务提供了大量功能,但并不是没有代价的。因为必须为所有的这些状态分配内存。如果不需要任务提供的附加功能,使用ThreadPool.QueueUserWorkItem,资源的使用效率会更高一些。Task类还实现了IDispose接口,允许你在用完Task对象后调用Dispose,不过大多数不管,让垃圾回收器回收就好。创建一个Task对象时,代表Task唯一的一个Int32字段初始化为零,TaskID从1开始,每分配一个ID都递增1。顺带说一下,在你调试中查看一个Task对象的时候,会造成调试器显示Task的ID,从而造成为Task分配一个ID。  这个ID的意义在于,每个Task都可以用一个唯一的值来标识。Visual Studio会在它的“并行任务”和并行堆栈“窗口中显示这些任务ID。要知道的是,这是Visual Studio自己分配的ID,不是在自己代码中分配的ID,几乎不可能将Visual Studio分配的ID和代码正在做的事情联系起来。要查看自己正在运行的任务,可以在调试的时候查看Task的静态CurrentId属性,如果没有任务在执行,CurrentId返回null。  再看看TaskStatus的值,这个可以查询Task对象的生存期:这些在任务运行的时候都是可以一一查到的,还有~判断要像这样:1 if(task.Status==TaskStatus.RantoCompletion)...为了简化编码,Task只提供几个只读Boolean属性:IsCanceled,IsFaulted,IsCompleted,它们能返回最终状态true/false。如果Task是通过调用某个函数来创建的,这个Task对象就会出于WaitingForActivation状态,它会自动运行。最后我们要来了解一下TaskFactory(任务工厂):

时间仓促,水平有限,如有不当之处,欢迎指正。

1.需要创建一组Task对象来共享相同的状态

2.为了避免机械的将相同的参数传给每一个Task的构造器。满足这些条件就可以创建一个任务工厂来封装通用的状态。TaskFactory类型和TaskFactory类型,它们都派生System.Object。你会学到不一样的编码方式:复制代码 1        static void Main(string[] args) 2        { 

3            Task parent = new Task(() => 

4            { 

5                var cts = new CancellationTokenSource(); 

6                var tf = new TaskFactory(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

7

8                //创建并启动3个子任务

9                var childTasks = new[] {

10            tf.StartNew(() => Sum(cts.Token, 10000)),

11            tf.StartNew(() => Sum(cts.Token, 20000)),

12            tf.StartNew(() => Sum(cts.Token, Int32.MaxValue))  // 这个会抛异常

13          };

14

15                // 任何子任务抛出异常就取消其余子任务

16                for (Int32 task = 0; task < childTasks.Length; task )

17                    childTasks[task].ContinueWith(t => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);

18

19                // 所有子任务完成后,从未出错/未取消的任务获取返回的最大值

20                // 然后将最大值传给另一个任务来显示最大结果

21                tf.ContinueWhenAll(childTasks,

22                    completedTasks => completedTasks.Where(t => !t.IsFaulted && !t.IsCanceled).Max(t => t.Result),

23                    CancellationToken.None)

24                    .ContinueWith(t => Console.WriteLine("The maxinum is: " t.Result),

25                      TaskContinuationOptions.ExecuteSynchronously).Wait(); // Wait用于测试

26            });

27

28            // 子任务完成后,也显示任何未处理的异常

29            parent.ContinueWith(p =>

30            {

31                // 用StringBuilder输出所有

32

33                StringBuilder sb = new StringBuilder("The following exception(s) occurred:" Environment.NewLine);

34                foreach (var e in p.Exception.Flatten().InnerExceptions)

35                    sb.AppendLine("  " e.GetType().ToString());

36                Console.WriteLine(sb.ToString());

37            }, TaskContinuationOptions.OnlyOnFaulted);

38

39            // 启动父任务

40            parent.Start();

41

42            try

43            {

44                parent.Wait(); //显示结果

45            }

46            catch (AggregateException)

47            {

48            }

49        }

50

51        private static Int32 Sum(CancellationToken ct, Int32 n)

52        {

53            Int32 sum = 0;

54            for (; n > 0; n--)

55            {

56                ct.ThrowIfCancellationRequested();

57                checked { sum = n; }

58            }

59            return sum;

60        }

61    }

复制代码

任务工厂就这么用,就是一个任务的集合。

现在看看TaskScheduler(任务调度)

任务基础结构是很灵活的,TaskScheduler对象功不可没。

TaskScheduler对象负责执行调度的任务,同时向Visual Studio调试器公开任务信息,就像一座桥梁,让我们能够掌控自己的任务线程。

TaskScheduler有两个派生类:thread pool task scheduler(线程池任务调度),和synchronization context task scheduler(同步上下文任务调度器)。默认情况下,所以应用程序使用的都是线程池任务调度器,这个任务调度器将任务调度给线程池的工作者线程。可以查询TaskScheduler的静态Default属性来获得对默认任务调度器的一个引用。

同步上下文任务调度器通常用于桌面应用程序,Winfrom,WPF及Silverlight。这个任务调度器将多有任务都调度给应用程序的GUI线程,使所有任务代码都能成功更新UI组建,比如按钮、菜单项等。同步上下文任务调度器根本不使用线程池。同样,可以查询TaskScheduler的静态FromCurrentSynchronizationContext方法来获得对一个同步上下文任务调度器的引用。

就像这样创建类型:

1 //同步上下文任务调度

2 TaskScheduler m_syncContextTaskScheduler =

3            TaskScheduler.FromCurrentSynchronizationContext();

任务调度有很多的,下面列举一部分,供参考,更多的请参看  它包括了大量的示例代码。

本文由澳门新萄京官方网站发布于www.8455.com,转载请注明出处:Net多线程编程,中TASK类的使用

关键词: