1. 程式人生 > >C#中的thread和task之Task

C#中的thread和task之Task

簡介

Task是在.NET Framework 4中新增進來的,這是新的namespace:System.Threading.Tasks;它強調的是adding parallelism and concurrency to applications

現在都是多核的CPU,在系統內,Task Parallel Library能更有效地利用CPU核心。TPL 會動態地按比例調節併發程度,以便最有效地使用所有可用的處理器。TPL 還處理工作分割槽、ThreadPool 上的執行緒排程、取消支援、狀態管理以及其他低級別的細節操作。對比ThreadPool上的Thread並沒有很好地支援Cancel的操作。

在語法上,和lambda表示式更好地結合。

建立Task

顯式建立Task

先看一個簡單的示例:

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

public class TaskExample1
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

        // 使用lambda方式,之間提供一個使用者delegate,建立一個Task

        Task taskA = new
Task( () => Console.WriteLine("From taskA.")); // Start taskA.Start(); // Output a message from the calling thread. Console.WriteLine("From thread '{0}'.", Thread.CurrentThread.Name); //等待task結束 taskA.Wait(); } } // From thread 'Main'.
// From taskA.

任務物件Task提供可在任務的整個生存期內從呼叫執行緒訪問的方法和屬性。例如,可以隨時訪問任務的 Status 屬性,以確定它是已開始執行、已完成執行、已取消還是引發了異常。狀態由 TaskStatus 列舉表示。

簡化建立並開始的Task操作版本–直接使用一個操作: System.Threading.Tasks.Task.Run()

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

public class TaskExample2
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("From taskA."));

      // Output a message from the calling thread.
      Console.WriteLine("From thread '{0}'.", 
                          Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
//       From thread 'Main'.
//       From taskA.

TaskFactory建立Task

TPL提供了工廠類TaskFactory,也可用直接建立Task。 一個操作System.Threading.Tasks.TaskFactory.StartNew, 即可完成建立並開始一個Task.

Use this method when creation and scheduling do not have to be separated and you require additional task creaion options or the use of a specific scheduler, or when you need to pass additional state into the task through its AsyncState property, as shown in the following example.

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

public class TaskExample3
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // 建立task並啟動
      Task taskA = Task.Factory.StartNew(() => Console.WriteLine("From taskA."));

      // 
      Console.WriteLine("From thread '{0}'.", 
                        Thread.CurrentThread.Name);
      // wait task
      taskA.Wait();                  
   }
}

//       Hello from thread 'Main'.
//       Hello from taskA.

傳入引數

來自MS的示例:

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


public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];

      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (int param) => {
                                                 Console.WriteLine("Task #{0}.", param);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);     
   }
}

Task返回值

如果一個Task執行方法是有返回值的,可用得到其值。在建立一個task時,其返回的值為Task,表示一個返回型別為T的Task;在taks完成執行後,可用通過Task的Result屬性獲取結果。

public class Task<TResult> : System.Threading.Tasks.Task

來自MS的示例:

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

class Program
{
    static void Main()
    {
        // lambda表示式建立一個task並執行,並返回值。
        Task<int> task1 = Task<int>.Factory.StartNew(() => 1);
        int i = task1.Result;

        // 返回物件
        Task<Test> task2 = Task<Test>.Factory.StartNew(() =>
        {
            string s = ".NET";
            double d = 4.0;
            return new Test { Name = s, Number = d };
        });
        Test test = task2.Result;

        // Return an array produced by a PLINQ query
        Task<string[]> task3 = Task<string[]>.Factory.StartNew(() =>
        {
            string path = @"C:\Users\Public\Pictures\Sample Pictures\";
            string[] files = System.IO.Directory.GetFiles(path);

            var result = (from file in files.AsParallel()
                          let info = new System.IO.FileInfo(file)
                          where info.Extension == ".jpg"
                          select file).ToArray();

            return result;
        });

        foreach (var name in task3.Result)
            Console.WriteLine(name);

    }
    class Test
    {
        public string Name { get; set; }
        public double Number { get; set; }

    }
}

Continue操作

如果一個Task開始執行時間依賴於其它的Task的完成,可以使用Continue系列方法。

來自MS的示例:

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {                         
      var getData = Task.Factory.StartNew(() => { 
                                             Random rnd = new Random(); 
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );  
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;

                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } ); 
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2, 
                                                                         x.Result.Item3);
                                                 } );                         
      Console.WriteLine(displayData.Result);
   }
}

// 輸出:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

而且,可以使用更簡化的寫法.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {                         
      var displayData = Task.Factory.StartNew(() => { 
                                                 Random rnd = new Random(); 
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).  
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;

                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ). 
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2, 
                                                             x.Result.Item3);
                                     } );                         
      Console.WriteLine(displayData.Result);
   }
}

Cancel操作

在TPL中,為Task能cancel執行,提供了CancellationTokenSource和CancellationToken;
需要完成的工作是:在Task的action執行方法內,週期性地檢查CancellationToken的IsCancellationRequested屬性。
示例:

    private var tokenSource = new CancellationTokenSource();

    public void Start()
    {
        var token = tokenSource.Token;
        for (int i = 0; i<5; i++>) 
        {
            Task t = Task.Factory.StartNew( () => DoSomeWork(i, token), token);
            Console.WriteLine("Task {0} executing", t.Id);
        }
    }

    public void Stop()
    {
        var token = tokenSource.Token;
        tokenSource.Cancel();
    }

    void DoSomeWork(int taskNum, CancellationToken ct)
    {
      // 先檢查,排程進入時,是否cancel了。
      if (ct.IsCancellationRequested == true) {
         Console.WriteLine("Task {0} was cancelled before it got started.",
                           taskNum);
         ct.ThrowIfCancellationRequested(); // 丟擲異常-- 或者 return
      } 

      // 正式開始任務。 
      int maxIterations = 100;

      // NOTE!!! A "TaskCanceledException was unhandled 
      for (int i = 0; i <= maxIterations; i++) {
         // 
         var sw = new SpinWait();
         for (int j = 0; j <= 100; j++)
            sw.SpinOnce();

         if (ct.IsCancellationRequested) {
            Console.WriteLine("Task {0} cancelled", taskNum);
            ct.ThrowIfCancellationRequested(); //丟擲異常-- 或者 return
         } 
      } 
    }