線程階段性總結——APM,ThreadPool,Task,TaskScheduler ,CancellationTokenSource

 

轉載:https://www.cnblogs.com/xiashengwang/archive/2012/08/18/2645588.htmlhtml

 

無論咱們使用thread,threadPool,task,仍是APM異步,本質都是在使用多線程。對於新手來講,不太敢用多線程的緣由,就我我的的體驗來講,就是對多線程的異常捕獲方式或時機缺少了解,而一旦出現異常沒有捕獲,將會帶來難以發現的bug,進而形成系統崩潰。而多線程自己也不是一朝一夕就能學好的,必須不斷的去學習總結,因此我我的認爲你要用一種線程模型,首先要對它有足夠的瞭解,特別是對異常的捕獲。若是你沒有徹底的把握,最好在實際開發中謹慎的用多線程。編程

1,APM異步編程模型。

採用BeginXXX和EndXXX方法。關於異常的捕捉,對於剛調用BeginXXX拋出的異常,異步操做可能尚未進入隊列。這種異常通常能夠忽略。對於進入異步操做時發生的異常,會將錯誤碼放入IAsyncResult對象中,在咱們調用EndXXX方法時,會將這個錯誤碼轉換成一個恰當的Exception再次拋出。因此對於APM編程模型來講,咱們只用對EndXXX方法進行異常捕捉。僞代碼:多線程

複製代碼
Try
 {
   Result = someObj.EndXXX(IAsyncResult);
}
Catch(xxxException e)
{
 //異常處理
}
複製代碼

注意事項:異步

1) 對於EndXXX方法的調用是必須的,不然可能會形成資源的泄漏,即便你可能不關心異步調用的返回結果,也要記住調用這個方法。異步編程

2) 只能調用一次EndXXX方法。函數

3) 調用EndXXX方法老是使用和BeginXXX時相同的對象。這裏辨別的是引用,引用不一樣就被視爲不一樣的對象。對於Delegate要補充一點,即便是相同簽名的委託,它們被編譯器編譯成具體的類,這些類的類名是不同的。學習

4) 不能取消異步I/O限制的異步操做。不要迷信這句話,他說的是I/O操做,是指的一個請求動做,若是咱們的是屢次請求,好比異步分塊上傳文件,是能夠作取消功能的。ui

5) FCL中有許多的I/O操做類都實現了APM。如派生自System.IO.Stream的類,Socket,Dns,WebRequest,還有SqlCommand等等。它們都提供了BeginXXX和EndXXX方法。spa

6) 能夠用APM來執行任何方法,咱們只須要定義一個與方法簽名一致的delegate,delegate編譯後會生成一個BeginInvoke和EndInvoke方法來支持APM操做。線程

2,Thread & ThreadPool

Thread和ThreadPool發起異步的缺點:

 1)沒有內建的機制知道任務什麼時候完成。

 2)無法獲得任務的返回值。

Thread的開銷太大,儘可能用ThreadPool,除非你要顯示指定你的thread爲前臺線程或要對線程設置優先級,不然就不要用thread。

注意:線程池是由全部的AppDomain共享的。一個CLR維持一個線程池。

3,Task

Task的引入,解決了上面的兩個問題。

1)Task可經過Wait()方法來等待任務的完成。這個方法是阻塞的。

2)經過Task.Result能夠獲得返回結果。在Result內部調用了Wait方法,因此查詢這個屬性是阻塞的。

3)對於任務函數的未處理異常,會被包裝成AggregateException異常拋出。可捕捉Wait()方法和Result屬性。經過AggregateException的InnerExceptions能夠進一步查詢具體的異常。

4)Task的靜態方法WaitAny和WaitAll能夠等待多個任務返回。一樣能夠捕捉這兩個方法的異常。

5)對於沒有調用Wait,Result,Exception來查詢未處理異常的狀況,例如:只調用了Task.Start方法。Task對象被回收時,Finalize方法會再次拋出這個異常終止進程。能夠向TaskScheduler.UnobservedTaskException事件註冊一個方法,來處理這類異常。經過UnobservedTaskExceptionEventArgs的SetObserved方法,能夠忽略掉這個異常,使進程不會終止。

6)構造Task時,能夠傳遞CancellationToken對象,以支持取消。若是是任務函數,經過調用CancellationToken.ThrowIfCancellationRequested 拋出的異常,類型是OperationCanceledException。若是任務函數沒有傳遞CancellationToken對象,那麼拋出的異常是TaskCanceledException,至關於任務級別的取消。

7) Task的ContinueWith方法能夠在第一個任務完成時開啓第二個任務。這個功能很強大,ContinueWith方法並不阻塞調用線程,它是異步的。咱們能夠在ContinueWith中寫一個事件回調方法,它能夠起到事件完成通知的做用。但它只能收到任務完成的通知,要實現任務進度的更新通知,到目前爲止,task依然作不到。

複製代碼
            Task<int> t = new Task<int>(() => Sum(100));

            t.Start();
            t.ContinueWith(task => Console.WriteLine("result:" + task.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
            t.ContinueWith(task => Console.WriteLine("canceled"), TaskContinuationOptions.OnlyOnCanceled);
            t.ContinueWith(task => Console.WriteLine("failed"), TaskContinuationOptions.OnlyOnFaulted);
複製代碼

對於上面的這一竄代碼,若是有未處理異常,一樣會形成進程終止。你一樣能夠用TaskScheduler.UnobservedTaskException事件註冊一個方法來處理。

8)Task能夠指定子任務,子任務沒有完成,父任務的ContinueTask也不會執行。關於異常和上面的處理方法同樣。由於這個也是不阻塞的,未處理異常暫時也只能在TaskScheduler.UnobservedTaskException裏處理。

複製代碼
           Task<Int32[]> parent = new Task<int[]>(() =>
                {
                    Int32[] result = new Int32[3];
                    new Task<Int32>(() => result[0] = Sum(100), TaskCreationOptions.AttachedToParent).Start();
                    new Task<Int32>(() => result[1] = Sum(200), TaskCreationOptions.AttachedToParent).Start();
                    new Task<Int32>(() => result[2] = Sum(300), TaskCreationOptions.AttachedToParent).Start();

                    return result;
                });

            parent.ContinueWith(parentTask => Array.ForEach(parentTask.Result, num => Console.WriteLine(num)));

            parent.Start();
複製代碼

9)TaskFactroy能夠簡化一組類似Task的建立工做。

複製代碼
Task parent = new Task(() =>
                {
                    CancellationTokenSource cts = new CancellationTokenSource();

                    TaskFactory<Int32> tf = new TaskFactory<Int32>(cts.Token, TaskCreationOptions.AttachedToParent,
                        TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

                    //create three child task
                    var childTasks = new[]{
                    tf.StartNew(()=>Sum(cts.Token,100)),
                    tf.StartNew(()=>Sum(cts.Token,200)),
                    tf.StartNew(()=>Sum(cts.Token,Int32.MaxValue))
                    };

                    //when one failed,cancel the other
                    for (int i = 0; i < childTasks.Length; i++)
                    {
                        childTasks[i].ContinueWith(task => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);
                    }

                    //display the maxvalue
                    tf.ContinueWhenAll(
                        childTasks, completeTasks => completeTasks.Where(
                            task => !task.IsCanceled && !task.IsFaulted).Max(t => t.Result),
                            CancellationToken.None).ContinueWith(task => Console.WriteLine("the max is:" + task.Result));

                });

            //show exception
            parent.ContinueWith(p =>
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("error occours:");
                foreach (var e in p.Exception.Flatten().InnerExceptions)
                {
                    sb.AppendLine(e.Message);
                }
                Console.WriteLine(sb.ToString());
            }, TaskContinuationOptions.OnlyOnFaulted);

            parent.Start();
複製代碼

4,對於協做取消要用CancellationTokenSource類。

1)CancellationTokenSource.Token方法返回CancellationToken,能夠將CancellationToken傳入咱們的工做方法,並查詢CancellationToken.IsCancellationRequested屬性來得到操做是否已取消。取消的狀況下,能夠結束工做方法。

2)通常在主線程調用取消方法。CancellationToeknSource.Cancel。

3)取消時能夠加入回調方法,經過CancellationToken.Register方法註冊。對於回調方法拋出的異常,能夠捕捉Cancel方法,異常會被包裝到AggregateException異常中,查詢InnerExceptions可的異常的詳細信息。

4)CancellationTokenSource的靜態方法CreateLinkedTokenSource能夠建立一個關聯的CreateLinkedTokenSource對象。任意其中的一個CreateLinkedTokenSource被取消,這個關聯的CreateLinkedTokenSource就會被取消。

5,任務調度器。

分爲線程池任務調度器(thread pool task scheduler)和同步上下文任務調度器(synchroliazation context task scheduler)。其中同步上下文任務調度器能將全部的任務調度給UI線程,這對於更新界面的異步操做至關有用!默認的調度器是線程池任務調度器。

非UI線程更新UI界面會報錯,能夠用下面的方法,指定同步上下文任務調度器:

複製代碼
        TaskScheduler syncSch = TaskScheduler.FromCurrentSynchronizationContext();

            Task<int> t = new Task<int>(() => Sum(100));

            //update UI with Synchronizationcontext
            t.ContinueWith(task => Text = task.Result.ToString(), syncSch);

            t.Start();
複製代碼

6,非UI線程更新UI界面的方式總結

詳見個人另外一篇文章:

http://www.cnblogs.com/xiashengwang/archive/2012/08/18/2645541.html

7,Parallel

這個類提供了For,Foreach,Invoke靜態方法。它內部封裝了Task類。主要用於並行計算。

複製代碼
        private void ParallelTest2()
        {
            for (int i = 1; i < 5; i++)
            {
                Console.WriteLine(DoWork(i));
            }
            //和上面的代碼等價,可是是多線程並行執行的,注意這裏的結束index不包含5
            var plr = Parallel.For(1, 5, i => Console.WriteLine(DoWork(i)));
        }

        private int DoWork(int num)
        {
            int sum = 0;
            for (int i = 0; i <= num; i++)
            {
                sum += i;
            }
            return sum;
        }
複製代碼
相關文章
相關標籤/搜索