一篇文章弄懂C#中的async和await

前言

本文介紹async/Task。在學習該知識點過程中,一定要按照每一個示例,去寫代碼、執行、輸出結果,自己嘗試分析思路。

async

微軟文檔:使用 async 修飾符可將方法、lambda 表達式或匿名方法指定為異步。

使用 async 修飾的方法,稱為異步方法。

例如:

為瞭命名規范,使用 async 修飾的方法,需要在方法名稱後面加上 Async 。

public async Task<int> TestAsync()  
{  
    // . . . .  
}
Lambda :


        static void Main()
        {
            Thread thread = new Thread(async () =>
            {
                await Task.Delay(0);
            });
        }
        public static async Task<int> TestAsync() => 666;

await

微軟文檔:await 運算符暫停對其所屬的 async 方法的求值,直到其操作數表示的異步操作完成。

異步操作完成後,await 運算符將返回操作的結果(如果有)。

好的,到此為止,async 和 await ,在官方文檔中的說明,就這麼多。

從以往知識推導

這裡我們不參考文檔和書籍的資料,不要看文檔和書籍中的示例,我們要一步步來從任務(Task)中的同步異步開始,慢慢摸索。去分析 async 和 await 兩個關鍵字給我們的異步編程帶來瞭什麼樣的便利。

創建異步任務

場景:周六日放假瞭,可以打王者(一種遊戲),但是昨天的衣服還沒有洗;於是用洗衣機洗衣服,清洗期間,開一局王者(一種遊戲)。

我們可以編寫一個方法如下:

        static void Main()
        {
            Console.WriteLine("準備洗衣服");

            // 創建一個洗衣服的任務
            Task<int> task = new Task<int>(() =>
            {
                // 模擬洗衣服的時間
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });

            Console.WriteLine("開始洗衣服");

            // 讓洗衣機洗衣服
            task.Start();

            Console.WriteLine("我去打王者,讓洗衣機洗衣服");
            // 打王者
            Thread.Sleep(TimeSpan.FromSeconds(4));
            Console.WriteLine("打完王者瞭,衣服洗完瞭嘛?");

            Console.WriteLine(task.IsCompleted);
            if (task.IsCompleted)
                Console.WriteLine("洗衣服花的時間:" + task.Result);
            else
            {
                Console.WriteLine("在等洗衣機洗完衣服");
                task.Wait();
                Console.WriteLine("洗衣服花的時間:" + task.Result);
            }
            Console.WriteLine("洗完瞭,撈出衣服,曬衣服,繼續打王者去");
        }

創建異步任務並返回Task

上面的示例,雖然說,異步完成瞭一個任務,但是這樣,將代碼都放到 Main ,可讀性十分差,還要其它什麼規范之類的,不允許我們寫這樣的垃圾代碼。於是我們將洗衣服這個任務,封裝到一個方法中,然後返回 Task 即可。

在 Program 類中,加入如下一個方法,這個方法用於執行異步任務,並且返回 Task 對象。

        /// <summary>
        /// 執行一個任務
        /// </summary>
        /// <returns></returns>
        public static Task<int> TestAsync()
        {
            Task<int> task = new Task<int>(() =>
            {
                // 模擬洗衣服的時間
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });
            task.Start();
            return task;
        }

Main 方法中,改成

        static void Main()
        {
            Console.WriteLine("準備洗衣服");

            // 創建一個洗衣服的任務
            Task<int> task = TestAsync();
            ... ...

但是,兩者差別還是不大。

異步改同步

我們創建瞭異步方法,去執行一個洗衣服的任務;當打完遊戲後,需要檢查任務是否完成,然後才能進行下一步操作,這時候就出現瞭 同步。為瞭保持同步和獲得執行結果,我們使用瞭 .Wait() 、.Result 。

這裡我們嘗試將上面的操作轉為同步,並且獲得執行結果。

    class Program
    {
        static void Main()
        {
            int time = Test();
            // ... ...
        }

        /// <summary>
        /// 執行一個任務
        /// </summary>
        /// <returns></returns>
        public static int Test()
        {
            Task<int> task = new Task<int>(() =>
            {
                // 模擬洗衣服的時間
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });
            task.Start();
            return task.Result;
        }
    }

說說 await Task

Task 和 Task<TResult> ,前者是一個沒有返回結果的任務,後者是有返回結果的任務。前面的文章中已經使用過大量的示例,這裡我們使用 await ,去完成一些完全相同的功能。

Task:

        public static void T1()
        {
            Task task = new Task(() => { });
            task.Wait();
        }
        public static async void T2()
        {
            Task task = new Task(() => {  });
            await task;
        }

說明,await 可以讓程序等待任務完成。

Task<TResult>:

       public void T3()
       {
           // 獲取 Task 任務對象,後面的邏輯過程可以弄成異步
           Task<int> task = TestAsync();

           // 任務是異步在執行,我不理會他
           // 這裡可以處理其它事情,處理完畢後,再獲取執行結果
           // 這就是異步

           Console.WriteLine(task.Result);
       }

        public async void T4()
        {
            // 使用 await 關鍵字,代表等待執行完成,同步
            int time = await TestAsync();
            Console.WriteLine(time);
        }

說明:await 可以讓程序等待任務執行完成,並且獲得執行結果。

看到沒有。。。await 關鍵字,作用是讓你等,是同步的,壓根不是直接讓你的任務變成異步後臺執行的。

那為啥提到 async 、await,都是說跟異步有關?不急,後面解釋。

說說 async Task<TResult>

async Task<TResult> 修飾一個方法,那麼這個方法要返回 await Task<TResult> 的結果。

兩種同步方式示例對比:

        public static int Test()
        {
            Task<int> task = new Task<int>(() =>
            {
                // 模擬洗衣服的時間
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });
            task.Start();
            return task.Result;
        }
        public static async Task<int> TestAsync()
        {
            Task<int> task = new Task<int>(() =>
            {
                // 模擬洗衣服的時間
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });
            task.Start();
            int time = await task;
            return time;
        }

同步異步?

問:async 和 await 不是跟異步方法有關嘛,為啥前面的示例使用瞭 await ,全部變成同步瞭?

問:使用 async 和 await 的方法,執行過程到底是同步還是異步?

答:同步異步都行,要同步還是異步,全掌握在你的手上。

  • 你使用 await 去調用一個異步方法,其執行過程就是同步。
  • 你獲取異步方法返回的 Task,就是異步。

最近筆者收到一些提問,有些讀者,使用 async 和 await 去編寫業務,想著是異步,可以提升性能,實際結果還是同步,性能一點沒有提升。通過下面的示例,你會馬上理解應該怎麼用。

首先,在不使用 async 和 await 關鍵字的情況下,我們來編寫兩個方法,分別實現同步和異步的功能,兩個方法執行的結果是一致的。

        /// <summary>
        /// 同步
        /// </summary>
        /// <returns></returns>
        public static int Test()
        {
            Task<int> task = new Task<int>(() =>
            {
                return 666;
            });
            task.Start();
            return task.Result;
        }
        
        /// <summary>
        /// 異步
        /// </summary>
        /// <returns></returns>
        public static Task<int> TestAsync()
        {
            Task<int> task = new Task<int>(() =>
            {
                return 666;
            });
            task.Start();
            return task;
        }

能不能將兩個方法合並在一起呢?想同步就同步,想異步就異步,這樣就不需要寫兩個方法瞭!

是可以的!通過 async 和 await 關鍵字,可以輕松實現!

合並後,代碼如下:

        /// <summary>
        /// 可異步可同步
        /// </summary>
        /// <returns></returns>
        public static async Task<int> TestAsync()
        {
            Task<int> task = new Task<int>(() =>
            {
                return 666;
            });
            task.Start();
            return await task;
        }

合並後,我們又應該怎麼在調用的時候,實現同步和異步呢?

筆者這裡給出兩個示例:

        // await 使得任務同步
        public async void T1()
        {
            // 使用 await 關鍵字,代表等待執行完成,同步
            int time = await TestAsync();
            Console.WriteLine(time);
        }

        // 直接獲得返回的 Task,實現異步
        public void T2()
        {
            // 獲取 Task 任務對象,後面的邏輯過程可以弄成異步
            Task<int> task = TestAsync();

            // 任務是異步在執行,我不理會他
            // 這裡可以處理其它事情,處理完畢後,再獲取執行結果
            // 這就是異步

            Console.WriteLine(task.Result);
        }

至此,理解為什麼使用瞭 async 和 await,執行時還是同步瞭吧?

Task封裝異步任務

前面,我們都是使用瞭 new Task() 來創建任務,而且微軟官網大多使用 Task.Run() 來編寫 async 和 await 的示例。

因此,我們可以修改前面的異步任務,改成:

        /// <summary>
        /// 可異步可同步
        /// </summary>
        /// <returns></returns>
        public static async Task<int> TestAsync()
        {
            return await Task.Run<int>(() =>
            {
                return 666;
            });
        }

關於跳到 await 變異步

在百度學習異步的時候,往往會有作者說,進入異步方法後,同步執行代碼,碰到 await 後就是異步執行。

當然還有多種說法。

我們已經學習瞭這麼多的任務(Task)知識,這一點十分容易解釋。

因為使用瞭 async 和 await 關鍵字,代碼最深處,必定會出現 Task 這個東西,Task 這個東西本來就是異步。碰到 await 出現異步,不是因為 await 的作用,而是因為最底層有個 Task。

為什麼出現一層層的 await

這是相對於提供服務者來說。因為我要提供接口給你使用,因此底層出現 async、await 後,我會繼續保留方法是異步的(async),然後繼續封裝,這樣就有多層的調用結構,例如上一小節的圖。

但是如果來到瞭調用者這裡,就不應該還是使用 async 、await 去編寫方法,而是應該按照實際情況同步或異步。

總結

到此這篇C#中async和await的文章就介紹到這瞭,更多相關C#中async和await內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: