簡體   English   中英

使用 async 和 await 關鍵字的好處

[英]Benefits of using async and await keywords

我是在 C# 中使用異步方法的新手。 我讀過這些關鍵字asyncawait有助於通過異步某些方法使程序更具響應性。 我有這個片段:

第一種方式

    public static void Main()
    {
        Console.WriteLine("Hello!! welcome to task application");
        Console.ReadKey();
        Task<string> ourtask = Task.Factory.StartNew<string>(() =>
        {
            return "Good Job";
        });
        ourtask.Wait();
        Console.WriteLine(ourtask.Result);
        Console.ReadKey();
    }

第二種方式

 public static void Main()
        {
            Launch();
        }
        public static async void Launch()
        {
            Console.WriteLine("Hello!! welcome to task application");
            Console.ReadKey();
            Console.WriteLine(await GetMessage());
            Console.ReadKey();
        }

        public static Task<string> GetMessage()
        {
            return Task.Factory.StartNew<string>(() =>
                {
                    return "Good Job";
                });
        }

我需要知道 :

  1. 兩種實現之間有區別嗎(在並行性的概念上)?

  2. 如果我可以創建一個任務並等待它完成,那么使用asyncawait關鍵字有什么好處?

假設您有一個邊境檢查站。 每輛車都可以一一通過,讓海關檢查他們的車,看看他們是否沒有走私任何比利時巧克力。

現在假設你在你的大眾甲殼蟲中排隊,在你是一輛 24 輪怪物卡車之前,你幾乎無法適應。 你現在被困在這個龐然大物后面很長一段時間,直到海關完成搜索,然后他們才能轉向你,他們基本上只需要拍拍你就可以告訴你你可以走了。

為了對抗這種效率,我們邊境巡邏隊的好朋友想出了一個主意,安裝了第二個檢查站。 現在他們可以通過兩倍的人,而您可以只帶那個人而不是在怪物卡車后面等待!

問題解決了對吧? 不完全是。 他們忘記創建通往該檢查站的第二條道路,因此所有車輛仍然必須通過單車道,導致卡車仍然擋住了甲殼蟲。

這與您的代碼有何關系? 很簡單:你也在做同樣的事情。

當您創建一個新Task您實際上是在創建第二個檢查點。 但是,當您現在使用.Wait()同步阻止它時,您正在迫使每個人都走那條路。

在第二個示例中,您使用await創建第二條道路並允許您的汽車與卡車同時處理。

我將嘗試直接回答問題:

  1. 您的示例(有效地)都沒有涉及任何並行性。 我看到它們之間的兩個主要區別:1)第一個示例將在任務在第二個線程上運行時阻塞一個線程,這是毫無意義的,2)第二個示例將提前退出。 一旦遇到await ,控制立即返回Main() ,並且由於您沒有等待從Launch()返回的任務完成,您的程序將在該點退出。

  2. 與等待任務完成相比,使用asyncawait的好處在於,當該任務正在運行時, await不會阻塞當前線程。 await ,只要編譯器遇到await ,它就會有效地將該方法的其余部分重寫為將在任務完成時調用的回調。 這將釋放當前線程以在任務運行時做其他事情,例如響應客戶端應用程序中的用戶輸入或為 Web 應用程序中的其他請求提供服務。

坦率地說,這不是一個很好的例子來展示async / await的好處。 您基本上是在說您想做 CPU 密集型工作,並且在該工作完成之前您不想做任何其他事情。 您也可以同步執行此操作。 異步在進行 I/O 綁定工作時真的很出色,例如通過網絡進行調用(使用正確實現的異步庫,例如HttpClient ),因為您不是像第二個示例中那樣簡單地將一個線程換成另一個; 從字面上看,該 I/O 綁定工作 沒有消耗 任何線程

正如其他人所提到的,並行性完全是另一個話題。 雖然async / await可以是幫助您實現它的有用結構,但涉及更多內容,在我看來,您最好在“繼續”並行化之前牢牢掌握線程釋放的好處。

同樣正如其他人所提到的,這是一個很大的話題,我強烈建議您查看一些很棒的資源。 因為我已經參考了 Stephen Cleary 的博客,所以我會繼續給它一個完整的插件 - 他的async/await 介紹和后續帖子是關於這個主題的優秀入門書。

我們有異步/等待編程的兩個主要好處

1- 非阻塞編程

當您有不需要阻止執行的長時間運行的操作時。 在這種情況下,您可以在等待長時間運行任務的結果的同時執行其他工作。

想象一下,我們有兩個程序流,它們可以並行工作而不會相互阻塞。

示例:假設我們需要記錄出現的每個錯誤,但同時這不應阻塞流,因此在這種情況下,我們可以同時記錄和返回消息。

2- async/await 編程中線程管理的好處

我們知道,在正常的編程(阻塞)中,即使我們有不同的流(兩個流沒有任何依賴關系),每一行代碼都會阻塞它之后的所有內容,直到它完成該過程。 但是在 async/await 編程中,應用程序不會阻塞這個線程,換句話說,他們會釋放它來做另一項工作,當函數完成工作時,任何空閑線程都會處理響應。

C# async 和 await:我們為什么需要它們?

async / await清理了大量復雜的代碼,這些代碼會過度使用Task.ContinueWith.ContinueWith.ContinueWith等等。

從編碼的角度來看,可視化、調試和維護 Task.ContinueWith 更加困難,包括必須隨附的相關異常處理。

所以, await出現了,給了我們這個

    public static void Main()
    {
        Launch();
    }
    public static async void Launch()
    {
        Console.WriteLine("Hello!! welcome to task application");
        Console.ReadKey();
        Console.WriteLine(await GetMessage());
        Console.ReadKey();
    }

    public static Task<string> GetMessage()
    {
        return Task.Factory.StartNew<string>(() =>
            {
                return "Good Job";
            });
    }

這幾乎相當於:

    public static void Main()
    {
        Launch();
    }
    public static async void Launch()
    {
        Console.WriteLine("Hello!! welcome to task application");
        Console.ReadKey();
        return  Task.Factory.StartNew(() => GetMessage())
            .ContinueWith((t) => 
                  {
                     Console.WriteLine(t.Result)
                     Console.ReadKey();
                  });
    }

    public static Task<string> GetMessage()
    {
        return Task.Factory.StartNew<string>(() =>
            {
                return "Good Job";
            });
    }

您可以從示例中看到 GetMessage()之后的所有內容都包含在 ContinueWith 中,但是該方法會在任務創建后立即返回。 所以它正在返回調用方法。

這里需要等待那個Task,否則程序會繼續退出:

Launch().Wait();

不必編寫 ContinueWith() 意味着我們的代碼變得更具可讀性,特別是在我們必須在一個方法中將多個 await 塊鏈接在一起的情況下,它會“讀”得很好。

同樣如前所述, await示例處理了更好的異常處理,否則我們將不得不使用 TPL 方法來處理異常,這也會使代碼庫過於復雜。

關於你的兩個例子,它們並不是真正等效的,所以你不能真正判斷一個。 但是, async/await相當於構造了 Tasks/ContinueWith。

我將 async/await 視為 TPL 向實際語言本身的演變。 一種語法糖。

邊境檢查站的比喻,我會說:

同步邊境員工

您有 4 條進線車道和 2 名軍官。 處理即將到來的怪物卡車的官員傑克不知道怪物卡車的確切規則,所以他打電話給辦公室。 現在,如果他同步工作,他會一直打電話等待回復。 所以他無法處理其他任何事情——他的同事瑪麗將不得不這樣做。

幸運的是,Mary 並行工作,因此她可以同時處理 3 條暢通的車道。 然而,因為她也是同步工作,所以她一次只能處理一輛車。 因此,當她必須檢查邊車上有獵豹的摩托車的規則時,她必須打電話給總辦公室,並且還要接聽電話。

現在我們有兩條車道被待處理的工作擋住了,還有兩條車道因為沒有員工而被擋住了。

異步邊境員工

現在,如果 Jack 異步工作 - 他將不會排隊等待響應。 相反,他掛了電話,走到第二條車道,處理另一輛車,同時等待總公司回電。 因為第二車道是一個非常緊張的女士,口吃,這需要他相當長的時間。

但幸運的是,瑪麗現在也在異步工作,當她處理完第二輛車(或暫停,因為她必須檢查獵豹)時,她可以在怪物卡車上接聽辦公室的回電。 這樣她就可以完成對怪物卡車的處理。
但當然,怪物卡車並沒有在她完成后立即消失——司機已經記錄了他在檢查站花費的時間。 幸運的是,Mary 仍然並行工作,因此她可以開始處理另一條車道上的汽車。

費用

然后有一天,火人節開始了,許多不尋常的車輛抵達邊境。 這些都需要大量的電話到辦公室,因此阻塞了所有 4 條車道。 所以傑克和瑪麗只能坐在那里等回車,而車輛的隊伍卻越來越多。

幸運的是,這個地區的土地便宜,所以他們的老板決定再增加 4 條車道。 雖然其中一些額外的通道也被阻塞,等待辦公室的回電,但至少傑克和瑪麗保持忙碌,不必坐等電話。 他們的老板當然可以考慮多雇一些人來減少堵車,但他知道他們需要住房和培訓,而且節日很快就結束了,所以他就這樣……

回顧

傾向於ASP.Net:

  • 通道(例如連接)很便宜,並且可以輕松擴展(並且您可能應該增加 IIS 中的連接限制和隊列大小以進行異步,例如參見下面的介紹參考)
  • 員工(線程)更昂貴(例如,請參閱下面的介紹參考))
  • 工作的“慢”部分(例如 I/O 或遠程 http 請求),不應該阻止您昂貴的員工(即應該使用異步)
  • 注意:工作可能會改變員工(雙關語),所以不要與你的員工保留數據。 例如,Jack 不應該把 Monstertruck-papers 放在他的口袋里,因為 Mary 可能會進一步處理它——他應該把它歸還,或者把它保存在一個依賴於工作的存儲中(不要在線程中存儲數據,而是在 HttpContext 中存儲數據)

文學

FWIW:我喜歡Stephen Cleary 2014 年的這篇技術介紹

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM