Parallel類(http://www.cnblogs.com/afei-24/p/6904179.html)的並行任務需要結束後才能運行後面的代碼,如果想不等結束後在開始動作,可以使用Task類更好地控制並行動作。 任務表示應完成的某個工作單元。這個工作單元可以在單獨的線程中運行,也可以以同步方 ...
Parallel類(http://www.cnblogs.com/afei-24/p/6904179.html)的並行任務需要結束後才能運行後面的代碼,如果想不等結束後在開始動作,可以使用Task類更好地控制並行動作。
任務表示應完成的某個工作單元。這個工作單元可以在單獨的線程中運行,也可以以同步方式啟動一個任務,這需要等待主調線程。使用任務不僅可以獲得一個抽象層,還可以對底層線程進行很多控制。
任務相對Parallel類提供了非常大的靈活性。例如,可以定義連續的工作——在一個任務完成後該執行什麼工作。這可以根據任務成功與否來分。還可以在層次結構中安排任務。例如,父任務可以創建新的子任務。
一.啟動任務
要啟動任務,可以使用TaskFactory類或Task類的構造函數和Start()方法。Task類的構造函數在創建任務上靈活性比較大。
在啟動任務時,會創建Task類的一個實例,利用Action或Action<T>委托(不帶參數或帶一個參數),可以指定應運行的代碼。
1.使用線程池的任務
線程池提供了一個後臺線程的池(後面詳細介紹了線程池)。線程池獨自管理線程,根據需要增加或減少線程池中的線程數。線程池中的線程用於實現一些動作,之後仍然返回線程池中。
下麵介紹創建線程池的任務的四種方法:
先定義一個要調用使用的方法:
//避免寫入控制台的操作交叉,這裡使用lock關鍵字同步 static object taskMethodLock = new object(); static void TaskMethod(object title) { lock (taskMethodLock) { Console.WriteLine(title); Console.WriteLine("task id:{0},thread:{1}",Task.CurrentId,Thread.CurrentThread.ManagedThreadId); Console.WriteLine("is pooled thread:{0}",Thread.CurrentThread.IsThreadPoolThread); Console.WriteLine("is background thread:{0}",Thread.CurrentThread.IsBackground); } }
(1).使用實例化的TaskFactory類,把TaskMethod方法和TaskMethod方法的參數傳遞給StartNew方法:
var tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod,"using a task factory");
(2).使用Task類的靜態屬性Factory來訪問TaskFactory,以調用StartNew()方法。類似第一種,也使用了工廠,但對工廠的控制沒那麼全面。
Task t2 = Task.Factory.StartNew(TaskMethod,"using factory via a task");
(3).使用Task的構造函數。實例化Task對象時任務不會執行,只是指定Created狀態。接著調用Start()方法,啟動任務。
Task t3 = new Task(TaskMethod,"using a task constructor and Start");
t3.Start();
(4).直接調用Task類的Run()方法啟動任務。Run()方法沒有傳遞帶參數委托的版本,可以通過傳遞lambda表達式。
Task t4 = Task.Run(()=> TaskMethod("using Run method"));
static void Main(string[] args) { var tf = new TaskFactory(); Task t1 = tf.StartNew(TaskMethod,"using a task factory"); Task t2 = Task.Factory.StartNew(TaskMethod,"using factory via a task"); Task t3 = new Task(TaskMethod,"using a task constructor and Start"); t3.Start(); Task t4 = Task.Run(()=> TaskMethod("using Run method")); Console.ReadKey(); }
2.同步任務
任務不一定使用線程池中的線程,也可以使用其它線程。任務也可以同步運行,以相同的線程作為主調線程。
示例:
static void RunSyncTask() { TaskMethod("main thread"); var t = new Task(TaskMethod,"run sync"); t.RunSynchronously(); }
輸出:
上面代碼先在主線程上直接調用TaskMethod方法,然後在創建的Task上調用。從輸出看到,主線程是一個前臺線程,沒有任務ID,也不是線程池中的線程。調用RunSynchronously方法時,會使用相同的線程,會創建一個任務。
3.使用單獨線程的任務
上面將到的任務雖然不是線程池中的線程,但使用的是主線程,不是單獨的,不能實現非同步。
如果任務的代碼應該長時間運行,就應該使用TaskCreationOptions.LongRunning告訴任務調度器創建一個新的單獨線程,而不是線程池中的線程。這個線程可以不由線程池管理。當線程來自線程池時,任務調度器可以決定等待已經運行的任務完成,然後使用這個線程,而不是線上程池中創建一個新線程。對於長時間運行的線程,任務調度器會立即知道等待它們完成是不明智的做法,會創建一個新的線程。
示例:
static void LongRunTask() { var t = new Task(TaskMethod,"long running",TaskCreationOptions.LongRunning); t.Start(); }
輸出:
二.任務的結果————Future
任務結束時,可以把一些有用的狀態信息寫入共用對象中。這個共用對象必須是線程安全的。另一個選項是使用返回某個結果的任務。這種任務也叫future,因為它在將來返回一個結果。這需要使用Task類的一個泛型版本。使用這個類可以定義任務返回的結果的類型。
示例:
使用泛型類Task<TResult>,TResult是返回類型。通過構造函數,把方法傳遞給Func委托,第二個參數是委托的參數。
static void Main(string[] args) { var t = new Task<Tuple<int, int>>(TaskWithResult,Tuple.Create<int,int>(8,3)); t.Start(); Console.WriteLine(t.Result); t.Wait(); Console.WriteLine("result from task:{0},{1}", t.Result.Item1, t.Result.Item2); Console.ReadKey(); }
由任務來調用來返回結果的方法可以聲明為任何類型。
static Tuple<int, int> TaskWithResult(object o) { Tuple<int, int> div = (Tuple<int, int>)o; int result = div.Item1 / div.Item2; int reminder = div.Item1 % div.Item2; Thread.Sleep(10000); return Tuple.Create<int, int>(result,reminder); }
這裡使用了元組(http://www.cnblogs.com/afei-24/p/6738155.html).
三.連續的任務
通過任務,可以指定在任務完成後,應接著運行另一個特定任務。例如,一個使用前一個任務的結果的新任務,如果前一個任務失敗了,這個任務就應執行一些清理工作。
任務處理程式(前一個任務)或者不帶參數,或者帶一個對象參數,而連續處理程式有一個Task類型的參數,這裡可以訪問前一個任務的相關信息。
示例:
//一個任務結束時,可以啟動多個任務,連續任務也可以有另一個連續的任務。 static void Main(string[] args) { Task t1 = new Task(DoFirst); Task t2 = t1.ContinueWith(DoSecond); Task t3 = t1.ContinueWith(DoSecond); Task t4= t2.ContinueWith(DoSecond); t1.Start(); Console.ReadKey(); } static void DoFirst() { Console.WriteLine("do some task:{0}",Task.CurrentId); Thread.Sleep(3000); } static void DoSecond(Task t) { Console.WriteLine("task {0} finished",t.Id); Console.WriteLine("this task id:{0}",Task.CurrentId); }
無論前一個任務是如何結束,前面的連續任務總是在前一個任務結束時啟動。使用TaskContinuationOptions枚舉中的值,可以指定,連續任務只有在任務成功或失敗時啟動。
Task t5 = t1.ContinueWith(DoSecond,TaskContinuationOptions.OnlyOnFaulted);
四.任務的層次結構
利用任務連續性,可以在一個任務結束後啟動另一個任務。任務也可以構成一個層次結構。在一個任務中啟動一個新的任務時,就啟動了一個父/子層次結構。取消父任務,也會取消子任務。
創建子任務與創建父任務的代碼相同,唯一區別就就是子任務從另一個任務內部創建。
示例:
static void Main(string[] args) { Task t = new Task(ParentTask); t.Start(); Console.ReadKey(); } static void ParentTask() { Console.WriteLine("parent task id:{0}",Task.CurrentId); var child = new Task(ChildTask); child.Start(); Console.WriteLine("parent create child"); } static void ChildTask() { Console.WriteLine("child task"); }
如果父任務在子任務之前結束,父任務的狀態就是WaitingForChildrenToComplete。所有子任務也結束時,父任務的狀態就是RanToCompletion.