C# 異步編程之 Task 的使用


(說明:隨筆內容為學習task的筆記,資料來源:https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?redirectedfrom=MSDN&view=netframework-4.7.2,下面內容的圖片大多來自msdn,不是的會說明)

一、什么是task?

Task 是一個獨立的操作線程,通常是異步執行的。通過Task啟動的異步操作線程是在線程池中執行的,也即Task使用的是線程池的線程。

測試一下Task使用線程池的證明:

代碼如下:

        public static void ThreadPoolTest()
        {
            Action action1 = () => { Console.WriteLine("I am Action, I don't need anything"); };
            for (int i = 0; i < 100; i++)
            {
                new Task(action1).Start();
            }
        }

        public static void ThreadPoolTest2()
        {
            Action action1 = () => { Console.WriteLine("I am Action, I don't need anything"); };
            for (int i = 0; i < 1000; i++)
            {
                new Thread(new ThreadStart(Print)).Start();
            }
        }

        private static void Print()
        {
            Console.WriteLine("I am Action, I don't need anything");
        }

運行結果:

 

這個是使用Windows Performence Monitor(性能監視器)監視的系統當前的線程數量情況,綠色的線代表的是當前應用程序托管的線程,包括正在運行的和已經停止的。圖中綠色的線急劇上升的時候是我開始運行ThreadPoolTest2方法的時候(下降是我關閉程序的時候),而在后面當線程恢復平穩我運行ThreadPoolTest方法的時候線程的數量卻沒有什么變化。

 二、Task的構造函數:

 

我們主要看以下幾個:
1、Task(Action):

Action是沒有返回值的委托。

簡單來說,直接貼代碼,這個是很簡單的一個操作,也很常用:

        public static void ActionParameter()
        {
            Action action1 = () => { Console.WriteLine("I am Action, I don't need anything"); };
            Action<object> action2 = (o) => { Console.WriteLine("I am Action, My Name is:"+o); };
            Task task1 = new Task(action1);
            Task task2 = new Task(action2,"a");
            Task task3 = new Task(action2, "b");

            task1.Start();
            task2.Start();
            task3.Start();
        }

2、Task(Action, CancellationToken) 

先上例子:

 CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
            var token = cancellationTokenSource.Token;
            List<int> intList = new List<int>();
            for (int i = 0; i < 1000; i++)
            {
                intList.Add(i);
            }

            Task t = Task.Run(() => {

                Parallel.ForEach(intList, 
                    i => {
                        Console.WriteLine(i);
                        Interlocked.Increment(ref num);
                        Console.WriteLine("num is:" + num);
                        if (token.IsCancellationRequested)
                            token.ThrowIfCancellationRequested();

                    }
                );
            }, token);

            Thread.Sleep(5);
            cancellationTokenSource.Cancel();
            try
            {
                await t;
            }
            catch (AggregateException aggregateExceptions)
            {
                foreach (var ex in aggregateExceptions.InnerExceptions)
                {
                    Console.WriteLine(ex.GetType().Name + "========================================");
                }
            }
            finally
            {
                cancellationTokenSource.Dispose();
            }

運行結果:

250
0
num is:2
500
num is:3
num is:1
750
num is:4
OperationCanceledException========================================
OperationCanceledException========================================
OperationCanceledException========================================
OperationCanceledException========================================

通過上面例子可以看到:
1、進行取消用的是 CancellationTokenSource (官方文檔定義:向應該被取消的 System.Threading.CancellationToken 發送信號。)的屬性Token(類型是CancellationToken);

2、我們在子線程中是用Parallel(用於並行編程,同時啟用多個線程運行),在Parallel中當檢查到當前線程已經取消都拋出了異常,但是我們try catch這些異常的地方是在await,這個是很重要的一點,而且,同時啟動多個線程,異常是放在AggregateException 中;

3、我們有一行代碼Thread.Sleep(5),讓主線程睡眠了5毫秒,這個是為了讓子線程能夠獲得時間片。如果不這么做很大的可能會使await等待的是一個已經取消的異步操作,會拋出TaskCanceledException異常,需要進行捕獲。有興趣可以嘗試。

4、記得釋放CancellationTokenSource 

也可以在內部取消線程(僅展示修改的代碼塊):

            Task t = Task.Run(() => {

                Parallel.ForEach(intList,
                    i => {
                        Console.WriteLine(i);
                        Interlocked.Increment(ref num);
                        if (token.IsCancellationRequested)
                            token.ThrowIfCancellationRequested();
                        if (num > 30)
                        {
                            cancellationTokenSource.Cancel();
                        }
                        Console.WriteLine("num is:" + num);
                    }
                );
            }, token);

            try
            {
                await t;
            }

 3、Task(Action, TaskCreationOptions)

Action我們已經了解了,主要看的是TaskCreationOptions,我們知道,這種命名的一般是枚舉類型,看一下有什么枚舉值,官網有解釋:

3.1 AttachedToParent  

看一下區別:

看示例代碼:

 public static async void CreateOptionTest()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Task task = new Task(() =>
            {

                Task childTask = new Task(() =>
                {
                    try
                    {
                        Thread.Sleep(1000);
                        Console.WriteLine("child task is completed--------------------------");
                        throw new ChildIsCompletedException("just need a exception");
                    }
                    catch (ChildIsCompletedException ex)
                    {
                        throw ex;
                    }
                }
                //,TaskCreationOptions.AttachedToParent
                );
                childTask.Start();

                Thread.Sleep(200);
                Console.WriteLine("parent task is completed============================");
            }, TaskCreationOptions.None);

            task.Start();
            try
            {
                await task;
                Console.WriteLine("parent task status:"+task.IsCompleted);
            }catch(AggregateException aggreEx)
            {
                IReadOnlyCollection<Exception> exs = aggreEx.InnerExceptions;
                foreach (var item in exs)
                {
                    Console.WriteLine(item.GetType().Name);
                }
            }
        }

有添加AttachedToParent的運行結果:

parent task is completed============================
child task is completed--------------------------
ChildIsCompletedException

沒有添加的結果:

parent task is completed============================
parent task status:True
child task is completed--------------------------

沒有添加到父任務的時候是沒有拋出異常的,而且父任務完成了但是它還沒有退出,需要等待子任務完成才算完成,然而子任務拋出了異常,所以它就掛了,拋出了子任務的異常,而不附加到父任務的時候,就沒有異常拋出,父任務正常完成了。

 3.2  DenyChildAttach 使父任務拒絕任何嘗試的子任務附加;

在3.1的例子中,我們父任務的TaskCreationOptions的值是None,改成DenyChildAttach,即使子任務使用了AttachedToParent,輸出結果也會是像沒有添加一樣。

 

4、 屬性:

 4.1 AsyncState:

AsyncState為傳入線程的參數,類型是Object,以委托Action創建線程為例,委托Action可以傳入一個參數,AsyncState就是這個參數,請見:

        public static void GetAsyncStateTest()
        {
            Func<int> func = () => 1*2;
            Task task = new Task((x) => { Console.WriteLine("I am Action"); }, "I am AsyncState");
            task.Start();
            Console.WriteLine(task.AsyncState);
        }

運行結果:

I am AsyncState
I am Action

 

 

4.2 CompletedTask  (與FromResult<T>(T object)相比)

CompletedTask和FromResult都返回一個狀態為RanToCompletion的Task,區別是一個有返回值一個沒有。當我們允許一個線程需要返回一個task的時候,如果在早期就已經知道了結果線程不需要繼續往下運行可以根據需不需要返回值使用這兩個中的一個。

public static void GetCompletedTask()
        {
            Func<Task<string>> funcTask = () =>
             {
                 for (int i = 0; i < 1000; i++)
                 {
                     if (i > 100)
                     {
                         return Task.FromResult<string>("已完成");
                     }
                 }

                 return new Task<string>((x) => { return (string)x; }, "全部完成");
             };

            Func<Task> funcTaskNotResult = () =>
            {
                for (int i = 0; i < 1000; i++)
                {
                    if (i > 100)
                    {
                        return Task.CompletedTask;
                    }
                }

                return new Task<string>((x) => { return (string)x; }, "全部完成");
            };

            Task<string> task=Task.Run<string>(funcTask);
            Task taskNotResult = Task.Run(funcTaskNotResult);
            Thread.Sleep(1000);
            Console.WriteLine("task result:");

            Console.WriteLine("status:"+task.Status);
            Console.WriteLine("is completed:"+task.IsCompleted);
            Console.WriteLine("result"+task.Result);
            Console.WriteLine("taskNotResult result:");
            Console.WriteLine("status:" + taskNotResult.Status);
            Console.WriteLine(taskNotResult.IsCompleted);
        }

返回結果:

task result:
status:RanToCompletion
is completed:True
result已完成
taskNotResult result:
status:RanToCompletion
True

 

 

 

 持續更新中 ing


免責聲明!

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



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