C#基礎系列——異步編程初探:async和await


前言:前面有篇從應用層面上面介紹了下多線程的幾種用法,有博友就說到了async, await等新語法。確實,沒有異步的多線程是單調的、乏味的,async和await是出現在C#5.0之后,它的出現給了異步並行變成帶來了很大的方便。異步編程涉及到的東西還是比較多,本篇還是先介紹下async和await的原理及簡單實現。

C#基礎系列目錄:

 

之前的那篇 C#基礎系列——多線程的常見用法詳解 就講到了多線程new Thread()的方式對於有返回值類型的委托是沒有解決方案的,如果需要返回值,必須要依靠異步的方式。了解異步之前,我們先來看看Thread對象的升級版本Task對象:

1、Task對象的前世今生:Task對象是.Net Framework 4.0之后出現的異步編程的一個重要對象。在一定程度上來說,Task對象可以理解Thread對象的一個升級產品。既然是升級產品,那它肯定有他的優勢,比如我們上面Thread對象不能解決的問題:對於有返回值類型的委托。Task對象就能簡單的解決。

     static void Main(string[] args)
        {
            Console.WriteLine("執行GetReturnResult方法前的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            var strRes = Task.Run<string>(() => { return GetReturnResult(); });//啟動Task執行方法
            Console.WriteLine("執行GetReturnResult方法后的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            Console.WriteLine(strRes.Result);//得到方法的返回值
            Console.WriteLine("得到結果后的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));

            Console.ReadLine();
        }

        static string GetReturnResult()
        {
            Thread.Sleep(2000);
            return "我是返回值";
        }

先來看結果:

從結果分析可知在執行var strRes = Task.Run<string>(() => { return GetReturnResult(); })這一句后,主線程並沒有阻塞去執行GetReturnResult()方法,而是開啟了另一個線程去執行GetReturnResult()方法。直到執行strRes.Result這一句的時候主線程才會等待GetReturnResult()方法執行完畢。為什么說是開啟了另一個線程,我們通過線程ID可以看得更清楚:

     static void Main(string[] args)
        {
            Console.WriteLine("執行GetReturnResult方法前的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            var strRes = Task.Run<string>(() => { return GetReturnResult(); });
            Console.WriteLine("執行GetReturnResult方法后的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            Console.WriteLine("我是主線程,線程ID:" + Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(strRes.Result);
            Console.WriteLine("得到結果后的時間:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));

            Console.ReadLine();
        }

        static string GetReturnResult()
        {
            Console.WriteLine("我是GetReturnResult里面的線程,線程ID:" + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000);
            return "我是返回值";
        }

結果:

由此可以得知,Task.Run<string>(()=>{}).Reslut是阻塞主線程的,因為主線程要得到返回值,必須要等方法執行完成。

Task對象的用法如下:

            //用法一
            Task task1 = new Task(new Action(MyAction));
          //用法二
            Task task2 = new Task(delegate
            {
                MyAction();
            });
        //用法三
            Task task3 = new Task(() => MyAction());
            Task task4 = new Task(() =>
            {
                MyAction();
            });

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

由上可知,Task對象的構造函數傳入的是一個委托,既然能傳入Action類型的委托,可想而知Action的16中類型的參數又可以派上用場了。於是乎Task對象參數的傳遞就不用多說了吧。詳見 C#基礎系列——委托和設計模式(一)里面Action委托的用法。

 

2、初識 async & await。

        static void Main(string[] args)
        {
            Console.WriteLine("我是主線程,線程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            TestAsync();
            Console.ReadLine();
        }

        static async Task TestAsync()
        {
            Console.WriteLine("調用GetReturnResult()之前,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            var name = GetReturnResult();
            Console.WriteLine("調用GetReturnResult()之后,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            Console.WriteLine("得到GetReturnResult()方法的結果:{0}。當前時間:{1}", await name, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
        }

        static async Task<string> GetReturnResult()
        {
            Console.WriteLine("執行Task.Run之前, 線程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            return await Task.Run(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine("GetReturnResult()方法里面線程ID: {0}", Thread.CurrentThread.ManagedThreadId);
                return "我是返回值";
            });
        }

結果:

我們來看看程序的執行過程:

由上面的結果可以得到如下結論:

(1)在async標識的方法體里面,如果沒有await關鍵字的出現,那么這種方法和調用普通的方法沒什么區別。

(2)在async標識的方法體里面,在await關鍵字出現之前,還是主線程順序調用的,直到await關鍵字的出現才會出現線程阻塞。

(3)await關鍵字可以理解為等待方法執行完畢,除了可以標記有async關鍵字的方法外,還能標記Task對象,表示等待該線程執行完畢。所以await關鍵字並不是針對於async的方法,而是針對async方法所返回給我們的Task。

(4)是否async關鍵字只能標識返回Task對象的方法呢。我們來試試:

異步方法的返回類型必須為void、Task或者Task<T>類型。也就是說async要么是void,要么和Task關聯。

 

3、除了await關鍵字,Task對象還有另外一種方式等待執行結果。  

     static async Task TestAsync()
        {
            Console.WriteLine("調用GetReturnResult()之前,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            var name = GetReturnResult();
            Console.WriteLine("調用GetReturnResult()之后,線程ID:{0}。當前時間:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            Console.WriteLine("得到GetReturnResult()方法的結果:{0}。當前時間:{1}", name.GetAwaiter().GetResult(), DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
        }

這樣可以得到相同的結果。

name.GetAwaiter()這個方法得到的是一個TaskAwaiter對象,這個對象表示等待完成的異步任務的對象,並提供結果的參數。所以除了能完成await關鍵字的等待之外,它還能做一些其他的操作。我們將TaskAwaiter轉到定義

public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion
    {
        public bool IsCompleted { get; }
        public TResult GetResult();
        public void OnCompleted(Action continuation);
        public void UnsafeOnCompleted(Action continuation);
   }    

IsCompleted:獲取一個值,該值指示異步任務是否已完成。

GetResult():得到執行的結果。這個方法和await關鍵字效果相同。

OnCompleted():傳入一個委托,在任務執行完成之后執行。

UnsafeOnCompleted():計划與此 awaiter 相關異步任務的延續操作。

由此可以看出,await關鍵字實際上就是調用了TaskAwaiter對象的GetResult()方法。

 


免責聲明!

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



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