.NET 4.0 任务(Task)
隨著 .NET 4.0的到來,她與以前各版本的一個明顯差別就是并行功能的增強,以此來適應這個多核的世界。于是引入了一個新概念---任務,作為支持并行運算的重要組成部分,同時,也作為對線程池的一個補充和完善。從所周知,使用線程池有兩個明顯的缺點,那就是一旦把我們要執行的任務放進去后,什么時候執行完成,以及執行完成后需要返回值,我們都無法通過內置的方式而得知。由于任務(Task)的推出,使得我們對并行編程變得簡單,而且不用關心底層是怎么實現的,由于比線程池更靈活,如果能掌握好Task,對于寫出高效的并行代碼非常有幫助。
一、新建任務
??????在System.Threading.Tasks命名空間下,有兩個新類,Task及其泛型版本Task<TResult>,這兩個類是用來創建任務的,如果執行的代碼不需要返回值,請使用Task,若需要返回值,請使用Task<TResult>。
????? 創建任務的方式有兩種,一種是通過Task.Factory.StartNew方法來創建一個新任務,如:
????? Task task = Task.Facotry.StartNew(()=>Console.WriteLine(“Hello, World!”));//此行代碼執行后,任務就開始執行
????? 另一種方法是通過Task類的構造函數來創建一個新任務,如:
????? Task task = new Task(()=>Console.WriteLine(“Hello, World!”));//此處只把要完成的工作交給任務,但任務并未開始
????? task.Start();//調用Start方法后,任務才會在將來某個時候開始執行。
????? 同時,我們可以調用Wait方法來等待任務的完成或者調用IsCompleted屬性來判斷任務是否完成。需要說明的是,兩種創建任務的方法都可以配合TaskCreationOptions枚舉來實現我們對任務執行的行為具體控制, 同時,這兩種創建方式允許我們傳遞一個TaskCreationOptions對象來取消正在運行中的任務,請看任務的取消。
二、任務的取消
?????這世界唯一不變的就是變化,當外部條件發生變化時,我們可能會取消正在執行的任務。對于.NET 4.0之前,.NET并未提供一個內置的解決方案來取消線程池中正在執行的代碼,但在.NET 4.0中,我們有了Cooperative Cancellation模式,這使得取消正在執行的任務變得非常簡單。如下所示:
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;?
??????????????? }?
??????????? }?
??????? }?
??? }?
}
三、任務的異常機制
??? 在任務執行過程中產生的未處理異常,任務會把它暫時隱藏起來,裝進一個集合中。當我們調用Wait方法或者Result屬性時,任務會拋出一個AggregateException異常。我們可以通過調用AggregateException對象的只讀屬性InnerExceptions來得到一個ReadOnlyCollection<Exception>對象,它才是存儲拋出異常的集合,它的第一個元素就是最初拋出的異常。同樣的,AggregateException對象的InnerException屬性也會返回最初拋出的異常。
??? 值得重視的是,由于任務的隱藏機制的特點,一旦產生異常后,如果我們不調用相應的方法或者屬性查看異常,我們也無法判斷是否有異常產生(Task不會主動拋出異常)。當Task對象被GC回收時,Finalize方法會查檢是否有未處理的異常,如果不幸剛才好有,則Finalize方法會將此AggregateException再度拋出,如果再不幸,我們沒有捕獲處理這個異常,則我們的程序會立即中止運行。如果發生這樣的事情,會是多么大的災難啊!
??? 為了避免這種不幸的發生,我們可以通過注冊TaskScheduler類的靜態UnobservedTaskException事件來處理這種未被處理的異常,避免程序的崩潰。
四、任務啟動任務
????任務的強大與靈活之一是,當我們完成一個任務時,可以自動開始一個新任務的執行。如下所示:
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枚舉,得到更多我們想要的行為。
?
五、子任務
??? 任務是支持父子關系的,即在一個任務中創建新任務。如下所示:
using System;?
using System.Threading.Tasks;
namespace TaskDemo?
{?
??? class ChildTask?
??? {?
??????? static void Main()?
??????? {?
??????????? Task parant = new 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();?
??????? }?
??? }?
}
值得注意的是,以上代碼中所示的子任務的調用并不是以代碼的出現先后為順序來調用的。
六、任務工廠
???在某些情況下,我們會遇到創建大量的任務,而恰好這些任務共用某個狀態參數(如CancellationToken),為了避免大量的調用任務的構造器和一次又一次的參數傳遞,我們可以使用任務工廠來為我們處理這種大量創建工作。如下代碼所示:
using System;?
using System.Threading;?
using System.Threading.Tasks;
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))?
??????????????? };
??????????????? Thread.Sleep(5000);//此處睡眠等任務開始一定時間后才取消任務?
??????????????? cts.Cancel();?
??????????? }?
??????????? );
??????????? parent.Start();//開始執行任務?
??????????? Console.Read();?
??????? }
??????? static void ConcreteTask(CancellationToken token)?
??????? {?
??????????? while (true)?
??????????? {?
??????????????? if (!token.IsCancellationRequested)?
??????????????? {?
??????????????????? Thread.Sleep(500);?
??????????????????? Console.Write(".");?
??????????????? }?
??????????????? else?
??????????????? {?
??????????????????? Console.WriteLine("任務取消");?
??????????????????? break;?
??????????????? }?
??????????? }?
??????? }?
??? }?
}
七、任務調度程序
??? 任務的調度通過調度程序來實現的,目前,.NET 4.0內置兩種任務調度程序:線程池任務調度程序(thread pool task scheduler)和同步上下文任務調度程序(synchronization context task scheduler)。默認情況下,應用程序使用線程池任務調度程序調用線程池的工作線程來完成任務,如受計算限制的異步操作。同步上下文任務調度程序通常使用UI線程來完成與Windows Forms,Windows Presentation Foundation(WPF)以及SilverLight應用程序相關的任務。
?? 可喜的是,.NET 4.0 提供了TaskScheduler抽象類供開發人員繼承來實現自定義任務調度程序的開發,有興趣的同學可以試試。
八、總結
????? 任務給了我們更多的方便性、靈活性的同時,也帶來了比線程池更多的資源消耗。如果想減少資源消耗,請直接使用線程池QueueUserWorkItem方法效果會更好;如果想要更多的控制與靈活性,任務(Task)是不二的選擇。這個要我們開發者自己去斟酌了。
參考文獻:《CLR Via C#》,Third edtion, 作者:Jeffrey Richer,726頁-739頁
《Introducing .NET 4.0 With Visual Studio 2010》,作者:Alex Mackey,106頁-111頁
http://www.cnblogs.com/myshell/archive/2010/03/23/1692059.html
總結
以上是生活随笔為你收集整理的.NET 4.0 任务(Task)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 香薰公司名称起名大全(香气扑鼻!专业香薰
- 下一篇: C#并行编程中的Parallel.Inv