【轉】【C#】【Thread】【Task】多線程


多線程

多線程在4.0中被簡化了很多,僅僅只需要用到System.Threading.Tasks.::.Task類,下面就來詳細介紹下Task類的使用。

一、簡單使用

開啟一個線程,執行循環方法,返回結果。開始線程為Start(),等待線程結束為Wait()。

        /// <summary>
        /// Task簡單使用
        /// </summary>
        private void Demo1()
        {
            int i = 0;
            Random r = new Random(DateTime.Now.Second);
            Task t = new Task(() =>
            {
                for (int v = 0; v < 100; v++)
                    i += r.Next(100);
            });
            t.Start();
            t.Wait();
            Console.WriteLine("這是執行Task1后等待完成:" + i.ToString());
            Console.ReadLine();
        }

比以前使用Thread方便多了吧。

上面的例子是使用外部的變量獲得結果,下面的例子是用Task<T>直接返回結果,當調用Result屬性時,會自動等待線程結束,等同調用了Wait()。代碼如下:

        /// <summary>
        /// Task帶返回值
        /// </summary>
        private void Demo2()
        {
            Random r = new Random(DateTime.Now.Second);
            Task<int> t = new Task<int>(() =>
            {
                int i = 0;
                for (int v = 0; v < 100; v++)
                    i += r.Next(100);
                return i;
            });
            t.Start();
            Console.WriteLine("這是執行Task1獲取返回值:" + t.Result.ToString());
            Console.ReadLine();
        }

總結1:Task的使用比Thread簡單很多,減少了同步,等待等等問題,唯一的遺憾是不支持Thread的IsBackground。

結論1:如果不需要使用IsBackground,那么盡情的使用Task吧。經測試,Task是后台線程,也就是Isbackground=true;

 

二、線程執行完畢后調用另一個線程

也就是兩個線程,有序的執行,這里使用ContinueWith(),

t執行完畢后再執行一個task方法,不多說了代碼如下:

        /// <summary>
        /// Task 執行完畢后調用另一個Task
        /// </summary>
        private void Demo3()
        {
            Random r = new Random(DateTime.Now.Second);
            Task<int> t = new Task<int>(() =>
            {
                int i = 0;
                for (int v = 0; v < 100; v++)
                    i += r.Next(100);
                return i;
            });
            t.ContinueWith((Task<int> task) =>
            {
                Console.WriteLine("這是執行完畢Task1后繼續調用Task2:" + task.Result.ToString());
            });
            t.Start();
            Console.ReadLine();
        }

也可以直接鏈式的寫下去,代碼如下:

         /// <summary>
        /// Task 執行完畢后調用另一個Task(鏈式寫法)
        /// </summary>
        private void Demo4()
        {
            Random r = new Random(DateTime.Now.Second);
            Task<int> t = new Task<int>(() =>
            {
                int i = 0;
                for (int v = 0; v < 100; v++)
                    i += r.Next(100);
                return i;
            });
            Task t2 = t.ContinueWith((Task<int> task) =>
            {
                Console.WriteLine(task.Result.ToString());
            });
            t2.ContinueWith(task =>
            {
                Console.WriteLine("這是執行完畢Task1后繼續調用Task2,Task2后調用Task3。");
            });
            t.Start();
            Console.ReadLine();
        }

結論2:Task可以便捷的將幾個方法串行執行。

 

三、帶有父子關系的線程/多線程並行開啟

t帶有t1,t2,t3三個子線程,執行t的時候t1,t2,t3可並行處理,t必須等待t1,t2,t3都執行完畢后才能結束。

創建子Task時候必須指定參數為AttachedToParent。

        /// <summary>
        /// 帶有父子關系的Task集合,[TaskCreationOptions.AttachedToParent]
        ///
        /// 值                說明
        /// None              默認值,此Task會被排入Local Queue中等待執行,采用LIFO模式。
        /// AttachedToParent  建立的Task必須是外圍的Task的子Task,也是放入Local Queue,採LIFO模式。
        /// LongRunning       建立的Task不受Thread Pool所管理,直接新增一個Thread來執行此Task,無等待、無排程。
        /// PreferFairness    建立的Task直接放入Global Queue中,採FIFO模式。(比上面的優先級低)
        /// </summary>
        private void Demo5()
        {
            Task<int> t = new Task<int>(() =>
            {
                Task<int> t1 = new Task<int>(() =>
                {
                    int i = 0;
                    Random r = new Random(DateTime.Now.Second);
                    for (int v = 0; v < 100; v++)
                        i += r.Next(100);
                    return i;
                }, TaskCreationOptions.AttachedToParent);
                Task<int> t2 = new Task<int>(() =>
                {
                    int i = 0;
                    Random r = new Random(DateTime.Now.Second);
                    for (int v = 0; v < 100; v++)
                        i += r.Next(100);
                    return i;
                }, TaskCreationOptions.AttachedToParent);
                Task<int> t3 = new Task<int>(() =>
                {
                    int i = 0;
                    Random r = new Random(DateTime.Now.Second);
                    for (int v = 0; v < 100; v++)
                        i += r.Next(100);
                    return i;
                }, TaskCreationOptions.AttachedToParent);
                t1.Start();
                t2.Start();
                t3.Start();
                return t1.Result + t2.Result + t3.Result;
            });
            t.Start();
            t.Wait();
            Console.WriteLine("這是帶有父子關系的Task集合:" + t.Result.ToString());
            Console.ReadLine();
        }

結論3:多個線程的同時開啟在這里也很方便,也不用擔心同步等問題。

 

四、Task的中斷

這個很復雜,就不多說了,代碼中有比較詳細的介紹。

        /// <summary>
        /// 中途取消Task執行,Token
        ///
        /// 一是正常結束、二是產生例外、三是透過Cancel機制,這三種情況都會反映在Task.Status屬性上
        /// 值                              說明
        /// Created                         Task已經建立,但未呼叫Start。
        /// WaitingForActivation            Task已排入排程,但尚未執行(一般我們建立的Task不會有此狀態,只有ContinueWith所產生的Task才會有此狀態)。
        /// WaitingToRun                    Task已排入排程,等待執行中。
        /// Running                         Task執行中。
        /// WaitingForChildrenToComplete    Task正等待子Task結束。
        /// RanToCompletion                 Task已經正常執行完畢。
        /// Canceled                        Task已被取消。
        /// Faulted                         Task執行中發生未預期例外。
        ///
        /// 除了Status屬性外,Task還提供了另外三個屬性來判定Task狀態。
        /// 屬性            說明
        /// IsCompleted     Task已經正常執行完畢。
        /// IsFaulted       Task執行中法生未預期例外。
        /// IsCanceled      Task已被取消。
        /// </summary>
        private void Demo6()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            var ctoken = cts.Token;
            Task t1 = new Task(() =>
            {
                for (int v = 0; v < 10; v++)
                {
                    if (ctoken.IsCancellationRequested)
                    {
                        //第一種寫法
                        //這個會拋出異常
                        ctoken.ThrowIfCancellationRequested();
                        //另一種寫法
                        //這個不會返回異常,但是獲取不到是否是中斷還是執行完畢。
                        //return;
                    }
                    Thread.Sleep(1000);
                    Console.WriteLine(v);
                }
            }, ctoken);
            t1.Start();
            Thread.Sleep(2000);
            cts.Cancel();
            try
            {
                t1.Wait();
            }
            catch
            {
                if (t1.IsCanceled)
                    Console.WriteLine("cancel");
            }
            Console.ReadLine();
            cts.Dispose();
        }

結論4:中斷很復雜,但是對一般邏輯來說,是沒有很大必要的。

 

五、其他

這里介紹下另一種寫法Task.Factory,以及ContinueWhenAny和ContinueWhenAll兩個方法。

Task.Factory是靜態工廠類,用於對Task提供一些麻煩的支持,這里主要介紹ContinueWhenAny和ContinueWhenAll。

ContinueWhenAll所指定的函式會在傳入的所有Tasks結束時執行,只會執行一次。

ContinueWhenAny所指定的函式會在傳入的Tasks中有任何一個結束時執行,且與ContinueWhenAll相同,只會執行一次。

好了,還是看代碼:

        /// <summary>
        /// Task.Factory
        /// ContinueWhenAny和ContinueWhenAll
        /// ContinueWhenAll所指定的函式會在傳入的所有Tasks結束時執行,只會執行一次。
        /// ContinueWhenAny所指定的函式會在傳入的Tasks中有任何一個結束時執行,且與ContinueWhenAll相同,只會執行一次。
        /// </summary>
        private void Demo7()
        {
            Task<int> t1 = Task.Factory.StartNew<int>(() =>
            {
                int total = 0;
                for (int i = 0; i < 10; i++)
                    total += i;
                Thread.Sleep(12000);
                return total;
            });
            Task<int> t2 = Task.Factory.StartNew<int>(() =>
            {
                int total = 0;
                for (int i = 10; i < 20; i++)
                    total += i;
                Thread.Sleep(10000);
                return total;
            });
            Task tfinal = Task.Factory.ContinueWhenAny<int>(
                         new Task<int>[] { t1, t2 }, (Task<int> task) =>
                         {
                             if (task.Status == TaskStatus.RanToCompletion)
                             {
                                 Console.WriteLine(task.Result);
                             }
                         });
            Console.ReadLine();
        }

結論5:ContinueWhenAny和ContinueWhenAll對特定條件執行,還是有些用處的。

 

原文地址:http://www.cnblogs.com/sorex/archive/2010/09/18/1830130.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM