C# .Net并行(多核)编程

1 - Task的基本用法(创建与执行)  

Title :

  • Pro .NET 4 Parallel Programming in C# (Adam Freeman)
  • Task的基本用法

Quotes :

Task 代表一个异步操作。[From MSDN: Task class represents an asynchronous operation.]

System.Threading.Tasks.Task 是一个实现并行编程的基本的类。我们可以理解为是一个任务,它在某一个线程上执行,一个线程可以执行多个任务。


Sample 1: Hello Task

   1: static void Main(string[] args)
   2: {
   3:     Task.Factory.StartNew(() => Console.WriteLine("Hello task"));
   4:     Console.WriteLine("Main method complete. Press enter to finish.");
   5:     Console.ReadLine();
   6: }

这里用了Task的Factory属性返回一个TaskFactory对象,然后调用这个对象的StartNew方法创建并执行一个Task对象。(这里符合静态工厂(Static Factory)的设计模式)


Sample 2: 3种创建一个Task的方法

   1: public static void Main(string[] args)
   2: {
   3:     //使用Action代理创建
   4:     Task task1 = new Task(new Action(printMessage));
   5:
   6:     //使用一个匿名的代理创建
   7:     Task task2 = new Task(delegate()
   8:     {
   9:         printMessage();
  10:     });
  11:
  12:     //用一个lambda表达式创建
  13:     Task task3 = new Task(() => printMessage());
  14:
  15:     //执行Task
  16:     task1.Start();
  17:     task2.Start();
  18:     task3.Start();
  19:
  20:     Console.WriteLine("Main method complete. Press enter to finish.");
  21:     Console.ReadLine();
  22: }
  23: 
  24: static void printMessage()
  25: {
  26:     Console.WriteLine("Hello World!");
  27: }

Main方法主体在主线程中执行,task1,task2,task3在其它线程中运行,结果是输出3个 “Hello World!”。


Extends : Action代理

   1: public delegate void Action();
   2: public delegate void Action<in T>(T obj);

End

2 - Task的基本用法(设置任务方式与获取任务结果)

Title :

  • Pro .NET 4 Parallel Programming in C# (Adam Freeman) Task的基本用法


G 1 :

设置任务方式:public Task (Action<Object> action, Object state)

通过这个构造方法可以为任务执行体提供输入参数。action 代表任务执行过程,state 为该任务提供参数。

Sample 1 :

   1: public static void Main(string[] args)
   2: {
   3:     string[] items = new string[] { "Ggicci", "Apple", "Google", "Microsoft" };
   4:     Task[] tasks = new Task[items.Length];
   5:     for (int i = 0; i < tasks.Length; i++)
   6:     {
   7:         //items[i]为参数提供给前面的lambda表达式所代表的任务执行体
   8:         tasks[i] = new Task(obj => Console.WriteLine((string)obj), items[i]);
   9:         tasks[i].Start();
  10:     }
  11: 
  12:     Console.WriteLine("Press enter to finish.");
  13:     Console.ReadLine();
  14: }
该段代码创建了4个 Task,每个 Task 的执行过程就是输出一段字符串,而这段字符串就是由 items 提供的,每个 Task 都会接收一个 items 的参数。


G 2 :

获取任务结果

通过使用 Task 的泛型子类 Task<TResult>,TResult 是返回数据的类型。其构造方法和 Task 同样有 8 个,实现同样的功能,只不过比 Task 多加了一个可返回的数据结果。

看一个对比:public Task (Action<Object> action, Object state) 和 public Task<TResult>(Func<Object, TResult> function, Object state)

Sample 2 :

   1: using System;
   2: using System.Threading.Tasks;
   3: 
   4: namespace Parallel_TaskProgramming
   5: {
   6:     class Worker
   7:     {
   8:         public DateTime EmployedDate { get; set; }
   9:     }
  10: 
  11:     class Listing3_13
  12:     {
  13:         public static void Main(string[] args)
  14:         {
  15:             //创建一个能返回 int 型数据的 Task
  16:             //Func<int>为下面 lambda 表达式,其中调用了自定义的 WorkLevel 方法返回 int 值
  17:             //提供一个 Worker 的对象为执行体的参数
  18:             Task<int> task = new Task<int>(worker =>
  19:             {
  20:                 return WorkLevel((Worker)worker);
  21:             },
  22:             new Worker() { EmployedDate = new DateTime(2008, 3, 18) });
  23: 
  24:             task.Start();
  25:             //通过 Task 的 Result 属性获取任务执行体的返回值
  26:             Console.WriteLine("Level: {0}", task.Result);
  27:
  28:             Console.WriteLine("Press enter to finish.");
  29:             Console.ReadLine();
  30:         }
  31:         
  32:         //一个计算员工的工作等级的自定义方法
  33:         static int WorkLevel(Worker worker)
  34:         {
  35:             int level = 0;
  36:             int days = (DateTime.Today - worker.EmployedDate).Days;
  37:             while (days > 0)
  38:             {
  39:                 days -= (int)Math.Pow(2, level);
  40:                 level++;
  41:             }
  42:             return level;
  43:         }
  44:     }
  45: }
Result :
   1: Level: 11
   2: Press enter to finish.


End

3 - Task的基本用法(取消Task的执行)  

Title :

  • Pro .NET 4 Parallel Programming in C# (Adam Freeman) Task的基本用法
  • Task 的取消

Steps :


 

Quote :

You must also throw an instance of System.Threading.OperationCanceledException in your task body; this is how you acknowledge the cancellation, and if you forget, the status of your task will not be set correctly. [From “Pro .Net 4 Parallel Programming in C#”]

你必须在你的Task的执行体里面抛出一个System.Threading.OperationCanceledException异常。具体有两种形式 :

    1.    1: while(true) {
         2:     if(token.IsCancellationRequested) {
         3:         throw new OperationCanceledException(token);
         4:     } else {
         5:         //do your work
         6:     }
         7: }
       
    2.    1: while(true) {
         2:     token.ThrowIfCancellationRequested();
         3:     //do your work
         4: }

Simple Sample :

   1: using System;
   2: using System.Threading;
   3: using System.Threading.Tasks;
   4:  
   5: namespace Parallel_TaskProgramming
   6: {
   7:     class Program
   8:     {
   9:         public static void Main(string[] args)
  10:         {
  11:             //step 1: create a instance of System.Threading.CancellationTokenSource
  12:             CancellationTokenSource tokenSource = new CancellationTokenSource();
  13:             //step 2: call the CancellationTokenSource.Token property to get a System.Threading.CancellationToken
  14:             CancellationToken token = tokenSource.Token;
  15:             
  16:             //step 3: create a new Task or Task<T>
  17:             Task task = new Task(() => {
  18:                 int i = 0;
  19:                 while (true)
  20:                 {
  21:                     token.ThrowIfCancellationRequested();
  22:                     Console.WriteLine(i++);
  23:                 }                
  24:             }, token);
  25:         
  26:             //step 4: start the task and then you can call CancellationTokenSource.Cancel() to cancel the task
  27:             task.Start();
  28:             //主线程睡眠 5 ms
  29:             Thread.Sleep(5);
  30:             tokenSource.Cancel();
  31:             
  32:             Console.WriteLine("Press enter to finish.");
  33:             Console.ReadLine();
  34:         }
  35:     }
  36: }
Result : 结果是不确定的,5 ms 能打印多少次数字取决于运行时 CPU 的忙碌程度
   1: 0
   2: 1
   3: 2
   4: 3
   5: 4
   6: 5
   7: 6
   8: 7
   9: 8
  10: 9
  11: 10
  12: 11
  13: 12
  14: Press enter to finish.

Register : Monitoring Cancellation with a Delegate

CancellationToken 的 Register 方法可以为 token 登记一个 Action 或 Action<Object> 委托(代理)的实例。当 Task 被取消执行后,这个委托会被执行。

在上面 Simple Sample 的代码中的 task.Start() 前加上如下代码:

   1: token.Register(() =>
   2: {
   3:     Console.WriteLine("Task was canceled and then I'm invoked.");
   4: });

Result :

   1: 0
   2: 1
   3: 2
   4: 3
   5: 4
   6: Task was canceled and then I'm invoked.
   7: Press enter to finish.

WaitHandle : Monitoring Cancellation with a Wait Handle

CancellationToken 有个 WaitHandle 属性,这个属性是一个 WaitHandle 类的对象,WaitHandle 类的 WaitOne 方法的作用是:阻塞当前线程,直到当前的 WaitHandle 接收到一个信号。

Sample :

   1: public static void Main(string[] args)
   2: {
   3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
   4:     CancellationToken token = tokenSource.Token;
   5:  
   6:     Task task = new Task(() =>
   7:     {
   8:         Console.WriteLine("Task running...");
   9:         token.WaitHandle.WaitOne();
  10:         Console.WriteLine("Hello");
  11:     }, token);
  12:  
  13:     Console.WriteLine("Press enter to cancel the task.");
  14:     task.Start();
  15:     
  16:     //按空格键取消任务
  17:     Console.ReadLine();
  18:     tokenSource.Cancel();
  19:  
  20:     Console.WriteLine("Press enter to finish.");
  21:     Console.ReadLine();
  22: }

Result :

   1: Press enter to cancel the task.
   2: Task running...
   3:  
   4: Hello
   5: Press enter to finish.

Canceling Several Tasks : 取消多个任务

很简单,只需要在创建任务的时候用同一个 CancallationToken 的实例就可以了。


    End

    4 - Task的基本用法(复合的CancellationToken)  

    Title :

    • Pro .NET 4 Parallel Programming in C# (Adam Freeman) Task的基本用法
    • 复合的 Cancellation Token


    Quote :

    You can create a token that is composed from several CancellationTokens that will be cancelled if any of the underlying tokens is cancelled. [From “Pro .Net 4 Parallel Programming in C#”]

    你可以把一些 CancellationToken 组合在一起形成一个复合的 CancellationToken,只要其中任何一个 CancellationToken 被取消,那么这个复合的就会被取消。

    至于如何复合,只需调用 System.Threading.CancallationTokenSource.CreateLinkedTokenSource() 方法即可。


    Code :

       1: using System;
       2: using System.Threading;
       3: using System.Threading.Tasks;
       4:  
       5: namespace Parallel_TaskProgramming
       6: {
       7:     class Program
       8:     {
       9:         public static void Main(string[] args)
      10:         {
      11:             CancellationTokenSource tokenSource1 = new CancellationTokenSource();
      12:             CancellationTokenSource tokenSource2 = new CancellationTokenSource();
      13:             CancellationTokenSource tokenSource3 = new CancellationTokenSource();
      14:             //用多个CancallationToken复合
      15:             CancellationTokenSource compositeTokenSource =
      16:                 CancellationTokenSource.CreateLinkedTokenSource(
      17:                 tokenSource1.Token, tokenSource2.Token, tokenSource3.Token);
      18:  
      19:             Task task = new Task(() =>
      20:             {
      21:                 int i = 0;
      22:                 while (true)
      23:                 {
      24:                     if (compositeTokenSource.Token.IsCancellationRequested)
      25:                     {
      26:                         throw new OperationCanceledException(compositeTokenSource.Token);
      27:                     }
      28:                     else
      29:                     {
      30:                         Console.WriteLine(i++);
      31:                     }
      32:                 }
      33:             }, compositeTokenSource.Token);
      34:  
      35:             task.Start();
      36:             Thread.Sleep(5);
      37:             //只要其中一个子token被取消,任务就会被取消
      38:             tokenSource1.Cancel();
      39:  
      40:             Console.WriteLine("Press enter to finish.");
      41:             Console.ReadLine();
      42:         }
      43:     }
      44: }

    Result :

       1: 0
       2: 1
       3: 2
       4: 3
       5: 4
       6: 5
       7: 6
       8: 7
       9: 8
      10: Press enter to finish.


      End

      5 - WaitHandle.WaitOne 与 Thread.Sleep 区别

      Title :

      • Pro .NET 4 Parallel Programming in C# (Adam Freeman)
      • C# 多核编程中 System.Threading.WaitHandle.WaitOne() 与 System.Threading.Thread.Sleep() 的区别
      • C# 线程等待,睡眠


      Quote :

      The key difference with this technique is that cancelling the token doesn’t immediately cancel the task, because the Thread.Sleep() method will block until the time specified has elapsed and only then check the cancellation status. [From “Pro .Net 4 Parallel Programming in C#”]

      采用 Thread.Sleep() 不会立即取消 Task 的继续执行,因为它必须等到 Thread.Sleep(time) 所指定的 time 过去后才检查状态是否为已取消再去决定是否要取消 Task。

      The CancellationToken.WaitHandle.WaitOne() method returns true if the token has been cancelled and false if the time elapsed, causing the task has woken up because the time specified has elapsed.

      WaitHandle.WaitOne() 会返回 true 如果指定它的 token 已经被取消,会返回 false 如果它指定的时间已经过去。


      Code 1 : [ WaitHandle.WaitOne() ]

         1: public static void Main(string[] args)
         2: {
         3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:     CancellationToken token = tokenSource.Token;
         5: 
         6:     Task task = new Task(() =>
         7:     {
         8:         int i = 0;
         9:         while (true)
        10:         {
        11:             //如果 token 被取消,它会返回 true, 否则等 1000 ms 过去后返回 false
        12:             bool canceled = token.WaitHandle.WaitOne(1000);
        13:             Console.WriteLine(i++);
        14:             if (canceled)
        15:             {
        16:                 throw new OperationCanceledException(token);
        17:             }                   
        18:         }
        19:     }, token);
        20:     task.Start();
        21:     //主线程睡眠 5600 ms 后取消 token
        22:     Thread.Sleep(5600);
        23:     tokenSource.Cancel();
        24: 
        25:     Console.WriteLine("Press enter to finish.");
        26:     Console.ReadLine();
        27: }

      Result 1 :

         1: 0
         2: 1
         3: 2
         4: 3
         5: 4
         6: 5
         7: Press enter to finish.


      Code 2 : [ Thread.Sleep() ]

         1: public static void Main(string[] args)
         2: {
         3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:     CancellationToken token = tokenSource.Token;
         5:
         6:     Task task = new Task(() =>
         7:     {
         8:         int i = 0;
         9:         while (true)
        10:         {
        11:             //与 Code 1 比较,使用 Thread.Sleep()
        12:             Thread.Sleep(1000);
        13:             Console.WriteLine(i++);
        14:             token.ThrowIfCancellationRequested();
        15:         }
        16:     }, token);
        17:     task.Start();
        18:     //主线程同样睡眠 5600 ms 后取消 token
        19:     Thread.Sleep(5600);
        20:     tokenSource.Cancel();
        21:
        22:     Console.WriteLine("Press enter to finish.");
        23:     Console.ReadLine();
        24: }

      Result 2 :

         1: 0
         2: 1
         3: 2
         4: 3
         5: 4
         6: Press enter to finish.
         7: 5
      注意与 Result 1 的比较。解释:前面输出 0-4 这 5 个数字需要 5000 ms,主线程在 5600 ms 后调用 Cancel 方法把 Task 取消,可是在这之前 Task 已经进入第 6 个循环,但是此时 Thread.Sleep(1000) 的 1000 ms 才过去 600 ms,Thread.Sleep() 并不知道 Task 已经取消,所以等到剩余的 400 ms 过去后才输出 5 然后结束任务,所以主线程比它先结束。而对于 Code 1 ,在 Task 进入第 6 个循环的时候, WaitOne() 就因为在过了 600 ms 检测到 token 已经取消而返回 true, canceled 为 true, 所以 if 条件语句里面抛出异常,Task结束,打印 5 然后继续主线程中的打印 “Press enter to finish.”,主线程结束。


      End

      6 - 单个Task的等待

      Title :

      • Pro .NET 4 Parallel Programming in C# (Adam Freeman) - Waiting for a Single Task
      • C#多核编程 线程等待


      Quote :

      Task.Wait() 的多个重载:

      • Wait() 等待,直到 Task 完成,或者被取消,或者抛出异常
      • Wait(CancallationToken) 等待,直到 CancellationToken 被取消,或者同上
      • Wait(Int32) 等待,直到 Int32 代表的时间过了,或者同 Wait()
      • Wait(TimeSpan) 等待,直到 TimeSpan 所代表的时间过了,或者同 Wait()
      • Wait(Int32, CancellationToken) 等待,直到…(就是第二个和第三个的组合啦)


      Code 1 : [ Wait() ]

         1: public static void Main(string[] args)
         2: {
         3:    CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:    CancellationToken token = tokenSource.Token;
         5:  
         6:    Task task = new Task(() =>
         7:    {
         8:        token.ThrowIfCancellationRequested();
         9:        Thread.Sleep(1000);
        10:        Console.WriteLine("task running...");
        11:    }, token);
        12:    task.Start();
        13:  
        14:    //Console.WriteLine("Wait for task to complete.");
        15:    //task.Wait();
        16:  
        17:    Console.WriteLine("Press enter to finish.");
        18:    Console.ReadLine();
        19: }

      Result 1.1 : 下面的 “task running…”在 1000 ms 后打印

         1: Press enter to finish.
         2: task running...
      如果去掉代码中的注释部分,那么输出结果是:
      Result 1.2 : 主线程会等待 task 完成
         1: Wait for task to complete.
         2: task running...
         3: Press enter to finish.


      Code 2 : [ Wait(Int32) ]

         1: public static void Main(string[] args)
         2: {
         3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:     CancellationToken token = tokenSource.Token;
         5:  
         6:     Task task = new Task(() =>
         7:     {
         8:         token.ThrowIfCancellationRequested();
         9:         Thread.Sleep(1000);
        10:         Console.WriteLine("task running...");
        11:     }, token);
        12:     task.Start();
        13:  
        14:     //等待 task 500ms, 如果 500ms 内 task 完成, 那么会在 task 完成后马上返回 true
        15:     //如果 500 ms 内 task 没有完成, 那么返回 false
        16:     bool completed = task.Wait(500);
        17:     Console.WriteLine("Task completed? {0}", completed);
        18:  
        19:     Console.WriteLine("Press enter to finish.");
        20:     Console.ReadLine();
        21: }

      Result 2 : 500 ms 过去后打印 completed 的值,为 false,然后 task 在主线程结束后结束

         1: Task completed? False
         2: Press enter to finish.
         3: task running...


      Code 3 : [ Wait(Int32, CancellationToken) ]

         1: public static void Main(string[] args)
         2: {
         3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:     CancellationToken token = tokenSource.Token;
         5:  
         6:     Task task = new Task(() =>
         7:     {
         8:         bool canceled = token.WaitHandle.WaitOne(1000);
         9:         if (canceled)
        10:         {
        11:             throw new OperationCanceledException(token);
        12:         }
        13:         Console.WriteLine("task running...");
        14:     }, token);
        15:     
        16:     //timeTask 的任务是在 500 ms 过去的时候调用 tokenSource 的 Cancel 方法以取消 task
        17:     Task timeTask = new Task(() =>
        18:     {
        19:         Thread.Sleep(500);
        20:         tokenSource.Cancel();
        21:     });
        22:     task.Start();
        23:     timeTask.Start();
        24:  
        25:     try
        26:     {
        27:         //task 如果在执行过程中被取消, 那么就会抛出 OperationCanceledException
        28:         bool completed = task.Wait(2000, token);
        29:         Console.WriteLine("Task completed? {0}", completed);
        30:     }
        31:     catch (OperationCanceledException ex)
        32:     {
        33:         Console.WriteLine("Exception: {0}", ex.GetType());
        34:         
        35:     }
        36:  
        37:     Console.WriteLine("Press enter to finish.");
        38:     Console.ReadLine();
        39: }
      Result 3 :
         1: Exception: System.OperationCanceledException
         2: Press enter to finish.


      End

      7 - 多个Task的等待  

      Ttile :

      • Pro .NET 4 Parallel Programming in C# (Adam Freeman) - Waiting for Several Tasks, Waiting for One of Many Tasks
      • C#多核编程 - 线程等待


      Quote :

      Waiting for Several Tasks : 等待所有的 Task 完成

      You can wait for a number of tasks to complete by using the static Task.WaitAll() method. This method will not return until all of the tasks passed as arguments have completed, been cancelled, or thrown an exception. [ From “Pro .Net 4 Parallel Programming in C#”]

      使用 Task.WaitAll() 这个静态方法来实现等待多个 Task。这个方法只有在所有的 task 完成、被取消或者抛出异常的情况下才返回。

      Sample :

         1: public static void Main(string[] args)
         2: {
         3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:     CancellationToken token = tokenSource.Token;
         5:     
         6:     Task task1 = new Task(() => Console.WriteLine("Task 1 complete."));
         7:     Task task2 = new Task(() =>
         8:     {
         9:         //1s 后抛出一个异常
        10:         Thread.Sleep(1000);
        11:         throw new Exception();                
        12:     });
        13:     Task task3 = new Task(() =>
        14:     {
        15:         //3s 后打印
        16:         Thread.Sleep(3000);
        17:         Console.WriteLine("Task 3 complete.");
        18:     });
        19:     task1.Start();
        20:     task2.Start();
        21:     task3.Start();
        22:     try
        23:     {    
        24:         Task.WaitAll(task1, task2, task3);
        25:     }
        26:     catch (AggregateException ex) //捕捉 task2 抛出的异常
        27:     {
        28:         Console.WriteLine("Exception: {0}", ex.GetType());
        29:     }            
        30:  
        31:     Console.WriteLine("Press enter to finish.");
        32:     Console.ReadLine();
        33: }
      Result :
         1: Task 1 complete.
         2: Task 3 complete. //3s后打印出来
         3: Exception: System.AggregateException
         4: Press enter to finish.
      虽然 task2 在 1s 后就抛出了异常,但是 Task.WaitAll() 并没有马上终止并抛出异常,而是等到 3s 后 task3 结束。


      Quote :

      Waiting for One of Many Tasks : 等待多个 Task 中的任意一个完成就好

      The Task.WaitAny() method waits for one of a set of tasks to completeThe method waits until any of the specified Tasks completes and returns the array index of the completed Task. [ From “Pro .Net 4 Parallel Programming in C#”]

      Task.WaitAny() 等待多个 Task 中的最先一个完成的,然后返回其在多个 Task 中的位置/索引。

      Sample :

         1: public static void Main(string[] args)
         2: {
         3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:     CancellationToken token = tokenSource.Token;
         5:  
         6:     Task task1 = new Task(() =>
         7:     {
         8:         Thread.Sleep(3000);
         9:         Console.WriteLine("Task 1 complete.");
        10:     });
        11:     Task task2 = new Task(() =>
        12:     {
        13:         Thread.Sleep(1000);
        14:         Console.WriteLine("Task 2 complete.");
        15:     });
        16:     Task task3 = new Task(() =>
        17:     {
        18:         Thread.Sleep(2000);
        19:         Console.WriteLine("Task 3 complete.");
        20:     });
        21:     task1.Start();
        22:     task2.Start();
        23:     task3.Start();
        24:     //打印第一个完成的 Task 在作为数组参数传递给 Task.WaitAny() 时的序号/索引
        25:     Console.WriteLine("The first completed task indexed: {0}", Task.WaitAny(task1, task2, task3));
        26:  
        27:     Console.WriteLine("Press enter to finish.");
        28:     Console.ReadLine();
        29: }
      Result :
         1: Task 2 complete.
         2: The first completed task indexed: 1
         3: Press enter to finish.
         4: Task 3 complete.
         5: Task 1 complete.
      第一个完成的是 task2,索引为 1。


      End

      8 - Task的异常处理  

      Title :

      • Pro .NET 4 Parallel Programming in C# (Adam Freeman) – Handling Exceptions in Tasks
      • 如何处理 Task 运行过程中出现的异常


      Quote : [ From “Pro .Net 4 Parallel Programming in C#”- Table 2-7 ]

      Problem

      Solution

      Code

      Basic Exceptions

      Catch System.AggregateException when call trigger member(Task.Wait(), Task.WaitAll(), Task.WaitAny(), Task.Result) and get an enumerable AggregateException.InnerExceptions

      Code 1

      Iterative Handler

      Call the AggregateException.Handle() method, providing a delegate that takes a System.Exception and returns true if the exception has been handled and false if it should be escalated.

      Code 2

      Task Properties

      Call the IsCompleted, IsFaulted, IsCancelled and Exception properties of the Task class.

      Code 3

      Custom Escalation Policy

      Register an event handler with System.Threading.Tasks.TaskScheduler.UnobservedTaskException.

      Code 4


      Code 1 : Basic Exceptions

         1: public static void Main(string[] args)
         2: {
         3:     Task task1 = new Task(() => Console.WriteLine("Task 1: Hello World!"));
         4:     //task2 抛出一个异常
         5:     Task task2 = new Task(() =>
         6:     {
         7:         Exception ex = new NullReferenceException("I'm thrown by task2!");
         8:         Console.WriteLine("Task 2: Throw an exception: {0}", ex.GetType());
         9:         throw ex;
        10:     });
        11:  
        12:     task1.Start();
        13:     task2.Start();
        14:  
        15:     try
        16:     {
        17:         Task.WaitAll(task1, task2);
        18:     }
        19:     //捕捉 AggregateException
        20:     catch (AggregateException ex)
        21:     {
        22:         //从 InnerExceptions 属性获取一个可枚举的异常集合, 用 foreach 语句可以遍历其中包含的所有异常
        23:         foreach(Exception inner in ex.InnerExceptions)
        24:         {
        25:             Console.WriteLine("Exception: {0}\nType: {2}\nFrom {2}", 
        26:                                 inner.Message, inner.GetType(), inner.Source);
        27:         }
        28:     }
        29:  
        30:     Console.WriteLine("Press enter to finish.");
        31:     Console.ReadLine();
        32: }
      Result 1 :
         1: Task 1: Hello World!
         2: Task 2: Throw an exception: System.NullReferenceException
         3: Exception: I'm thrown by task2!
         4: Type: Parallel_TaskProgramming
         5: From Parallel_TaskProgramming
         6: Press enter to finish.
      State 1 :
      微软的 .NET 框架会把 Task 抛出的任何没有被 Catch 的异常储存起来,直到你调用了一些触发成员(trigger members)如 Task.Wait(), Task.WaitAll(), Task.WaitAny()或者Task.Result,然后这些成员会抛出一个 AggregateException 的实例。AggregateException 类型是为了包装一个或多个异常在一个实例里面,这是很实用的,我们想想,当你在调用 Task.WaitAll() 方法时候,所等待的 task 可能不止一个,如果这些 task 有多个抛出了异常,那么这些异常就应该全都被包装起来等我们处理。要遍历这个实例里面包含了的异常,可以获取它的 InnerExceptions 属性,它提供一个可枚举的容器(enumerable collection),里面容纳了所有的异常。


      Code 2 : Iterative Handler

         1: public static void Main(string[] args)
         2: {
         3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:     CancellationToken token = tokenSource.Token;
         5:  
         6:     //抛出 NullReferenceException
         7:     Task task1 = new Task(() =>
         8:     {
         9:         Exception ex = new NullReferenceException("I'm thrown by task2!");
        10:         Console.WriteLine("Task 2: Throw an exception: {0}", ex.GetType());
        11:         throw ex;
        12:     });
        13:     //抛出 OperationCanceledException 当 task2 接收到 tokenSource 的取消任务的信号的时候
        14:     Task task2 = new Task(() =>
        15:     {
        16:         token.WaitHandle.WaitOne();
        17:         Exception ex = new OperationCanceledException(token);
        18:     }, token);
        19:  
        20:     task1.Start();
        21:     task2.Start();
        22:     //发出取消任务执行的信号, task2 在这个时候检测到信号并抛出异常
        23:     tokenSource.Cancel();
        24:  
        25:     try
        26:     {
        27:         Task.WaitAll(task1, task2);
        28:     }
        29:     catch (AggregateException ex)
        30:     {
        31:         //利用 Handle 方法, 传递委托或者lambda表达式作为异常处理程序来处理自己感兴趣的异常并传递自己不感兴趣的异常
        32:         ex.Handle((inner) =>
        33:         {
        34:             if (inner is OperationCanceledException)
        35:             {
        36:                 Console.WriteLine("Ok, I'll handle the OperationCanceledException here...");
        37:                 return true;
        38:             }
        39:             else
        40:             {
        41:                 Console.WriteLine("No, I dont know how to handle such an exception, it will be propgated...");
        42:                 return false;
        43:             }
        44:         });
        45:     }
        46:  
        47:     Console.WriteLine("Press enter to finish.");
        48:     Console.ReadLine();
        49: }
      Result 2 :

       

      State 2 :

      上述代码中加粗的那段就是一个迭代的处理程序(Iterative Handler)的应用,Handle() 方法接受函数委托或者lambda表达式,用它们来处理异常。而且它们必须返回 true 或者 false。如果返回 true 说明你已经处理了异常,这个异常将不会被传递、抛出,返回 false 说明你无法处理异常,这个异常将继续传递下去。上述代码中处理了 OperationCancelledException,而对于 NullReferenceException 没有做出处理,那么它将继续被传递抛出并没有被捕捉,所以程序停止工作了。


      Code 3 : Task Properties

         1: public static void Main(string[] args)
         2: {
         3:     CancellationTokenSource tokenSource = new CancellationTokenSource();
         4:     CancellationToken token = tokenSource.Token;
         5:  
         6:     Task task1 = new Task(() =>
         7:     {
         8:         Exception ex = new NullReferenceException("I'm thrown by task2!");
         9:         Console.WriteLine("Task 2: Throw an exception: {0}", ex.GetType());
        10:         throw ex;
        11:     });
        12:  
        13:     Task task2 = new Task(() =>
        14:     {
        15:         token.WaitHandle.WaitOne();
        16:         Exception ex = new OperationCanceledException(token);
        17:     }, token);
        18:  
        19:     task1.Start();
        20:     task2.Start();
        21:  
        22:     tokenSource.Cancel();
        23:  
        24:     try
        25:     {
        26:         Task.WaitAll(task1, task2);
        27:     }
        28:     catch (AggregateException ex)
        29:     {
        30:         //把异常忽略过去...
        31:     }
        32:     //打印各种属性
        33:     Console.WriteLine("task1: IsCompleted({0}) IsCanceled({1}) IsFaulted({2}) Exception({3})",
        34:         task1.IsCompleted, task1.IsCanceled, task1.IsFaulted, task1.Exception);
        35:     Console.WriteLine("task2: IsCompleted({0}) IsCanceled({1}) IsFaulted({2}) Exception({3})",
        36:         task2.IsCompleted, task2.IsCanceled, task2.IsFaulted, task2.Exception);
        37:  
        38:     Console.WriteLine("Press enter to finish.");
        39:     Console.ReadLine();
        40: }

      Result 3 :

         1: Task 2: Throw an exception: System.NullReferenceException
         2: task1: IsCompleted(True) IsCanceled(False) IsFaulted(True) Exception(System.Aggr
         3: egateException: One or more errors occurred. ---> System.NullReferenceException:
         4:  I'm thrown by task2!
         5:    at Parallel_TaskProgramming.Program.<Main>b__0() in D:\文档\Visual Studio 201
         6: 0\Projects\C#\Parallel_TaskProgramming\Parallel_TaskProgramming\Program.cs:line
         7: 24
         8:    at System.Threading.Tasks.Task.InnerInvoke()
         9:    at System.Threading.Tasks.Task.Execute()
        10:    --- End of inner exception stack trace ---
        11: ---> (Inner Exception #0) System.NullReferenceException: I'm thrown by task2!
        12:    at Parallel_TaskProgramming.Program.<Main>b__0() in D:\文档\Visual Studio 201
        13: 0\Projects\C#\Parallel_TaskProgramming\Parallel_TaskProgramming\Program.cs:line
        14: 24
        15:    at System.Threading.Tasks.Task.InnerInvoke()
        16:    at System.Threading.Tasks.Task.Execute()<---
        17: )
        18: task2: IsCompleted(True) IsCanceled(True) IsFaulted(False) Exception()
        19: Press enter to finish.
      State 3 :
      这里的 IsCompleted 属性在 task 正常或者非正常结束后为 true,为 fasle 说明 task 还在运行;IsCanceled 属性在 task 被取消后为 true,否则为 false;IsFaulted 属性在 task 抛出异常后为 true, 否则为 false;Exception 属性在抛出异常后为该异常的引用,否则空。


      Code 4 : Custom Escalation Policy

         1: public static void Main(string[] args)
         2: {
         3:     //给 TaskScheduler.UnobservedTaskException 添加异常处理程序
         4:     TaskScheduler.UnobservedTaskException += 
         5:         (object sender, UnobservedTaskExceptionEventArgs eventArgs) => {
         6:             eventArgs.SetObserved();
         7:             ((AggregateException)eventArgs.Exception).Handle(ex =>
         8:             {
         9:                 Console.WriteLine("Exception Type: {0} Message : {1}", ex.GetType(), ex.Message);
        10:                 return true;
        11:             });
        12:         };
        13:  
        14:     Task task1 = new Task(() =>
        15:     {
        16:         throw new NullReferenceException("I'm thrown by task1!");
        17:     });
        18:  
        19:     Task task2 = new Task(() =>
        20:     {
        21:         throw new ArgumentOutOfRangeException("I'm thrown by task2!");
        22:     });
        23:  
        24:     task1.Start();
        25:     task2.Start();
        26:     //注意这里不能调用 Task.WaitAll() 等一些列的触发式成员方法
        27:     while (!task1.IsCompleted || !task2.IsCompleted)
        28:     {
        29:         Thread.Sleep(500);
        30:     }
        31:     Console.WriteLine("Press enter to finish.");
        32:     Console.ReadLine();
        33: }

      Result 4 :

         1: Press enter to finish.
      State 4 :
      注意这里并没有打印第 9 行代码的内容,这是因为在 task 没有被 GC 回收的时候,第 4 – 12 的代码是不会执行的。另外注意的是 27 行不能调用例如 Task.WaitAll( task1, task2 )这样的方法,这样的触发式成员会抛出异常导致异常不能被捕捉而使程序不能继续运行。关于第 4 – 12 行代码的执行问题,可参考:TaskScheduler.UnobservedTaskException never gets called - StackOverflow


      End

      Author : Ggicci

      此篇属于学习笔记,如有错误,欢迎指正!



      • 4
        点赞
      • 9
        收藏
        觉得还不错? 一键收藏
      • 0
        评论

      “相关推荐”对你有帮助么?

      • 非常没帮助
      • 没帮助
      • 一般
      • 有帮助
      • 非常有帮助
      提交
      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值