任務(Task)是一個管理並行工作單元的輕量級對象。它通過使用CLR的線程池來避免啟動專用線程,可以更有效率的利用線程池。System.Threading.Tasks 命名空間下任務相關類一覽:
類 作用 Task 管理工作單元 Task<TResult> 管理帶返回值的工作單元 TaskFactory 創建任務 TaskFactory<TResult> 創建任務或者有相同返回值的延續任務 TaskScheduler 管理任務調度 TaskCompletionSource 手動控制任務工作流任務用來並行地執行工作,充分地利用多核:事實上,Parallel和PLINQ內部就是建立在任務並行的結構上。
任務提供了一系列強大的特性來管理工作單元,包括:
同時任務實現了一個本地工作隊列,它允許你高效地創建快速執行的子任務而不用遭受在單個工作隊列時的競爭花費。任務並行庫讓你用最小的花費來創建成百上千的任務,但是如果你想創建上百萬個任務,就必須分割這些任務到更大的工作單元,以保持效率。
有兩種方法可以創建任務,一種是通過TaskFactory的StartNew()方法創建並啟動任務;另一種是調用Task構造函數創建,然後手動啟動任務。需要注意的是,任務啟動後並不會立即執行,它是由任務調度器(TaskScheduler)來管理的。
//沒有返回值 Task.Factory.StartNew(() => Console.WriteLine("Task Created!")); //有返回值 var task = Task.Factory.StartNew<string>(() => "Task Created!"); Console.WriteLine(task.Result);
var task = new Task<string>(() => "Task Created!"); task.Start();//異步執行 Console.WriteLine(task.Result);
var task = new Task<string>(() => "Task Created!"); task.RunSynchronously();//同步執行 Console.WriteLine(task.Result);
也可以在創建任務時指定一個任務狀態參數,可以通過任務的AsyncState屬性來訪問該參數。示例:
var task = Task.Factory.StartNew(state => "hello " + state, "Mike"); Console.WriteLine(task.AsyncState); Console.WriteLine(task.Result);
你還可以指定一個任務創建選項(TaskCreationOptions) ,這個枚舉類型有以下枚舉值:None,LongRunning,PreferFairness,AttachedToParent。下面解釋各個枚舉值的作用。
第一種方式: var parent = Task.Factory.StartNew(() => { var nonChildTask = Task.Factory.StartNew( () => Console.WriteLine("I'm not a child task.") ); var childTask = Task.Factory.StartNew( () => Console.WriteLine("I'm a child task."), TaskCreationOptions.AttachedToParent); }); 第二種方式: Task parent=new Task(()=> { DoStep1(); }); Task task2 = parent.ContinueWith ((PrevTask) => { DoStep2(); }); parent.Start();
任務可以通過Wait()成員方法或Result屬性來等待任務完成。
當調用Result屬性時,將會執行下列操作:
Task.WaitAny()靜態方法等待任何一個任務完成。示例:
var tasks = new Task[3]; for (int i = 0; i < tasks.Length; i++) { int taskIndex = i; tasks[i] = Task.Factory.StartNew(() => { int seed=Guid.NewGuid().GetHashCode(); int waitTime = new Random(seed).Next(10, 100); Thread.Sleep(waitTime); Console.WriteLine("Task{0} Finished", taskIndex); }); } Task.WaitAny(tasks); Console.WriteLine("已有任務完成");
Task.WaitAll()靜態方法等待所有任務完成。即使有任務拋出異常也不會終止等待,它會在所有任務完成之後拋出一個AggregateException異常,這個異常聚合了所有任務拋出的異常。示例:
var tasks = new Task[3]; for (int i = 0; i < tasks.Length; i++) { int taskIndex = i; tasks[i] = Task.Factory.StartNew(() => { int waitTime = new Random(Guid.NewGuid().GetHashCode()).Next(10, 100); Thread.Sleep(waitTime); Console.WriteLine("Task{0} Finished", taskIndex); }); } Task.WaitAll(tasks); Console.WriteLine("所有任務完成");
默認情況下任務未處理的異常會終止應用程序。需要指出的是任務中未處理的異常不會立即導致應用程序終止,異常要延遲到垃圾回收器回收任務並調用Finalize方法時才會終止程序。如果讀取了任務的Exception屬性,這個操作將阻止隨後的應用程序終止。 當等待任務完成時,所有未處理的異常會傳遞到調用方。 Wait()方法超時的異常也必須處理,否則導致應用程序終止。 子任務中未處理的異常會冒泡傳遞到父任務;嵌套任務中的非子任務的異常不會傳遞到這個任務的上一層任務,需要單獨處理,否則將導致應用程序終止。
var task = Task.Factory.StartNew(() => { Task.Factory.StartNew(() => { throw null; }, TaskCreationOptions.AttachedToParent); Task.Factory.StartNew(() => { throw null; }, TaskCreationOptions.AttachedToParent); Task.Factory.StartNew(() => { throw null; }, TaskCreationOptions.AttachedToParent); }); task.Wait();
TaskScheduler.UnobservedTaskException靜態事件提供了最後一種手段處理所有未處理異常。通過處理這個事件,就不用終止應用程序,而用你自己的異常處理邏輯替代它。
當創建任務時,可以傳入一個取消令牌(CancelationToken)參數,這樣就可以安全的取消任務。示例:
var source = new CancellationTokenSource(); var token = source.Token; var task = Task.Factory.StartNew(() => { Console.WriteLine("Task starting..."); while (true) { token.ThrowIfCancellationRequested(); Console.WriteLine("I'm alive. {0}",DateTime.Now); Thread.Sleep(1000); } },token); Task.Factory.StartNew(() => { Thread.Sleep(4500); source.Cancel(); }); try { task.Wait(); Console.WriteLine("Task stopped."); } catch (AggregateException e) { if (e.InnerException is OperationCanceledException) { Console.WriteLine("Task canceled."); } else { Console.WriteLine("errors."); } }
通過調用CancellationTokenSource的Cancel()方法取消任務,這個並不會立即終止任務,一直延遲到任務下次檢測是否取消時才通過拋出OperationCanceledException終止任務。
如果想通過直接拋出OperationCanceledException異常的方式取消任務,則需要在任務中傳入CancelationToken參數,否則就不能將任務的狀態為TaskStatus.Canceled並觸發OnlyOnCanceled延續任務。
此外,取消令牌也可以傳遞到Wait和CancelAndWait方法中來取消等待。