【譯】ASP.NET 中關(guān)于 Async 和 Await 的概述

原文地址 : 【A Simple Explanation of Async and Await in ASP.NET】

同步和異步.png

有時触机,我在日常工作中的技術(shù)分享迫使我去學(xué)習(xí)一些新的東西衷笋,有些甚至是以前從未接觸過的姑尺。上周就出現(xiàn)了這種情況,我的同事投票贊成下一次的技術(shù)分享主題為.NET 中的 Async 和 Await 異步編程纹蝴。這兩個關(guān)鍵字使得異步編程在 .NET 4.5 之后變得更簡單了血柳。

老實說辙纬,直到上一周,對于異步編程我知之甚少。但在我做了一些研究并創(chuàng)建了我自己的簡單項目之后囚聚,我開始理解了為什么 Stephen Cleary 會說 :“異步編程將從根本上改變大多數(shù)代碼的編寫方式”靖榕。

本著不關(guān)心我的結(jié)巴,只要我在學(xué)習(xí)(參見),我決定直接學(xué)習(xí) async , await 和 異步編程顽铸。 但是我遇到一個問題:我發(fā)現(xiàn)很少有資源能夠用簡單的術(shù)語向我解釋異步編程的概念茁计。

我試圖用這篇文章來彌補這個空缺。

我并不打算去詳細介紹 .NET 中關(guān)于異步編程的技術(shù)實現(xiàn)細節(jié)谓松,相反星压,我會專注于將這些不透明的概念分解成簡單的概念,使得它更好的被理解毒返。這幫助我更好的理解當我使用這些關(guān)鍵字時我正在做什么租幕,同時,希望它也能幫助到你拧簸。開始吧劲绪!

什么是異步編程?

異步編程是在編寫代碼盆赤,這些代碼允許在沒有“阻塞”的情況下同時發(fā)生幾件事情贾富,或者等待其它的事情完成。它不同于同步編程牺六,在同步編程中颤枪,所有的事情都按照它所寫的順序發(fā)生(如果你為一個活人編寫代碼,那么可能是同步代碼)淑际。

讓我們來看一個 C# 中同步編程的方法:

public string GetNameAndContent()
{
    var name = GetLongRunningName(); //調(diào)用另一個webservice畏纲,需要 1 分鐘。
    var content = GetContent();  //需要 30 秒
    return name + ": " + content;
}

每一次調(diào)用這個方法春缕,調(diào)用者必須等待一分鐘才能恢復(fù)處理盗胀。這一分鐘的時間被浪費了,它可以用來做其他的事情锄贼。

而 .NET 的異步編程票灰,我們可以像這樣改變這個方法:

public async Task<string> GetNameAndContent()
{
    var nameTask = GetLongRunningName(); //這個方法是異步的
    var content = GetContent(); //這個方法是同步的
    var name = await nameTask;
    return name + ": " + content;
}

我們改變了這個方法的三個地方:

  • 1.我們把方法改為了異步 async。它會告訴編譯器這個方法可以異步執(zhí)行宅荤。
  • 2.我們使用 await 關(guān)鍵字修飾 nameTask 變量屑迂,它告訴編譯器我們最終需要GetLongRunningName()方法的結(jié)果,但是我們不需要阻塞這個調(diào)用冯键。
  • 3.我們將方法的返回類型改為Task<string>惹盼。它通知調(diào)用者返回的最終類型為字符串,但不是立即獲得琼了,我們不可以做任何其他的事情直到GetLongRunningName()方法調(diào)用結(jié)束逻锐。

但是即使是這樣夫晌,它任然很模糊。當 “等待” GetLongRunningName()完成時昧诱,我們實際在做什么晓淀?

這很難用簡單的術(shù)語來表達,但我還是會嘗試的盏档。本質(zhì)上凶掰,系統(tǒng)希望執(zhí)行GetLongRunningName(),因為它首先被調(diào)用蜈亩,但它是一個異步任務(wù)懦窘,所以我們需要等待它,控制器被拋出去執(zhí)行GetContent()稚配,這意味著我們現(xiàn)在有兩種方法在同時運行畅涂。這不是轉(zhuǎn)移到另一個線程,使用 asyncawait不會導(dǎo)致線程的創(chuàng)建道川。

(如果您想要更深入地解釋在異步調(diào)用過程中發(fā)生了什么午衰,參見MSDN。這是我能找到的最好的例子冒萄,有一個圖臊岸。)

等待多個調(diào)用

讓我來看另一個簡單是例子。有一個叫 ContentManagement 的類尊流,它既包含了同步方法也包含了異步方法:

public class ContentManagement
{
    public string GetContent()
    {
        Thread.Sleep(2000);
        return "content";
    }

    public int GetCount()
    {
        Thread.Sleep(5000);
        return 4;
    }

    public string GetName()
    {
        Thread.Sleep(3000);
        return "Matthew";
    }
    public async Task<string> GetContentAsync()
    {
        await Task.Delay(2000);
        return "content";
    }

    public async Task<int> GetCountAsync()
    {
        await Task.Delay(5000);
        return 4;
    }

    public async Task<string> GetNameAsync()
    {
        await Task.Delay(3000);
        return "Matthew";
    }
}

ContentManagement 是一個簡單的類帅戒,它模擬了一些需要長時間運行的執(zhí)行方法。注意崖技,其中有三個方法被標注了 async逻住,并且(通過約定)將Async這個關(guān)鍵字添加到方法名中。我們將在下面的潛在問題部分中解釋為什么我們需要同步和異步方法迎献。

現(xiàn)在鄙信,然給我們寫一個如下的 MVC 控制器:

public class HomeController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        ContentManagement service = new ContentManagement();
        var content = service.GetContent();
        var count = service.GetCount();
        var name = service.GetName();

        watch.Stop();
        ViewBag.WatchMilliseconds = watch.ElapsedMilliseconds;
        return View();
    }

    [HttpGet]
    public async Task<ActionResult> IndexAsync()
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        ContentManagement service = new ContentManagement();
        var contentTask = service.GetContentAsync();
        var countTask = service.GetCountAsync();
        var nameTask = service.GetNameAsync();

        var content = await contentTask;
        var count = await countTask;
        var name = await nameTask;
        watch.Stop();
        ViewBag.WatchMilliseconds = watch.ElapsedMilliseconds;
        return View("Index");
    }
}

(順便說一下,Stopwatch類在嘗試記錄執(zhí)行時間時非常有用忿晕。)

注意這兩個動作的作用。在這兩種情況下银受,操作都調(diào)用 ContentManagement 服務(wù)中的方法践盼,但在其中一個操作中,它們是異步執(zhí)行的宾巍。

在我們的 Index 視圖中咕幻,我們有一個顯示 WatchMilliseconds 值的輸出。讓我們來看看Index中結(jié)果是什么:

直觀地說,這是有道理的顶霞。我們稱為三種方法;他們分別花了 2 秒肄程,5 秒和 3 秒來執(zhí)行;所以總執(zhí)行時間應(yīng)該是 2 + 5 + 3 = 10 秒锣吼。

現(xiàn)在看看如果我們調(diào)用 IndexAsync 操作會發(fā)生什么:

我們從哪里能得到這個數(shù)字? 這三個任務(wù)中的最長任務(wù)需要的時間為5秒蓝厌。 通過設(shè)計這個來使用async玄叠,我們能減少總花費時長的一半!通過編寫一些額外的代碼就可以獲得很好的加速效果!

返回類型

如你所見拓提,我們?yōu)槭裁匆帉懏惒骄幊檀a读恃,現(xiàn)在看來至少對我來說是有意義的。但是代态,到底我們在 IndexAsync 操作中使用的Task<ActionResult>是什么東西寺惫?

含有async關(guān)鍵字的方法中有三種返回類型可以選擇:

  • Task:這個類表示一個異步操作,并且可以被等待蹦疑;
  • Task<T>:這個類表示一個有返回值的異步操作西雀,并且可以被等待;
  • void:如果一個異步方法返回void歉摧,它不能被等待艇肴。這實際上把方法變成了“fire and forget(閱后即焚)”方法,這樣的情況很少出現(xiàn)判莉。進一步豆挽,返回void的異步方法的錯誤處理有點不同,比如shown by Stephen Cleary券盅。沒有理由使用void作為異步調(diào)用的返回類型帮哈,除非完全不關(guān)心調(diào)用是否實際完成。

簡而言之锰镀,幾乎所有的異步方法都會使用 Task 或 Task<T> 作為它們的返回類型娘侍。Task 類表示異步操作本身,而不是action的結(jié)果泳炉。在一個 Task 中調(diào)用 await 意味著我們需要等待這個 Task 執(zhí)行完成憾筏,而在 Task<T> 的情況下,需要檢索任務(wù)返回的值花鹅。

潛在問題

讓我們注意一些事情:大多數(shù)應(yīng)用程序在實現(xiàn)異步編程時可能不會看到這樣的戲劇性的改進(比如50%加速!)氧腰,同樣,我們不會對單個的方法進行壓力測試刨肃,只是同時執(zhí)行它們古拴。事實上,如果我們不正確地設(shè)計我們的異步方法真友,實際上可能會損害整體性能黄痪。

當我們將一個方法標記為 async 時,編譯器在后臺生成一個狀態(tài)機盔然;這是額外的代碼桅打。如果我們編寫好的是嗜、穩(wěn)定的異步代碼,創(chuàng)建這種額外結(jié)構(gòu)所需的時間不會對我們造成任何影響挺尾,因為運行異步的好處超過了構(gòu)建狀態(tài)機的成本鹅搪。然而,如果我們的 async 方法沒有 await潦嘶,方法將會被同步運行涩嚣,我們將花費額外的時間來創(chuàng)建我們沒有使用的狀態(tài)機。

還有一個潛在的問題需要注意掂僵。我們不能從同步方法調(diào)用異步方法航厚。因為在所有情況下, asyncawait應(yīng)該是一起的锰蓬,我們需要在所有的方法上都有異步幔睬。這就是為什么我們在前面的 ContentManagement 類中需要單獨的 async 方法。最終芹扭,這導(dǎo)致了更多的代碼麻顶,這意味著更多的東西理論上可以中斷。然而舱卡,由于對我們想要完成的事情有了良好的設(shè)計和堅實的理解辅肾,擁有額外的代碼也會帶來更大的性能,所以在我看來轮锥,這是一個公平的交易矫钓。

摘要

在實現(xiàn)異步的過程中付出一點額外的努力,對于提高我們的應(yīng)用程序的性能和響應(yīng)能力有很長的一段路要走舍杜。.NET使 asyncawait關(guān)鍵字變得容易新娜,我們可以簡單、簡潔地既绩、快速地實現(xiàn)異步設(shè)計概龄。

我在這個練習(xí)之前完全不懂異步編程;現(xiàn)在我至少可以說我不是一無所知饲握。希望你也一樣私杜!

如果你想學(xué)更多的內(nèi)容,可以看一下 Alex Davies 寫的 《Async in C# 5.0》 ,我在試圖理解MVC web應(yīng)用程序中異步/等待的情況時救欧,我讀過歪今,它幫了我很多。

我使用 Visual Studio 2015 和 ASP.NET MVC(包括我們之前使用的ContentManagement類和HomeController) 創(chuàng)建了一個簡單項目颜矿。我把它放到了Github,你可以check out下來進行練習(xí)嫉晶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骑疆,一起剝皮案震驚了整個濱河市田篇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌箍铭,老刑警劉巖泊柬,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诈火,居然都是意外死亡兽赁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門冷守,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刀崖,“玉大人,你說我怎么就攤上這事拍摇×燎眨” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵充活,是天一觀的道長蜂莉。 經(jīng)常有香客問我,道長混卵,這世上最難降的妖魔是什么映穗? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮幕随,結(jié)果婚禮上蚁滋,老公的妹妹穿的比我還像新娘。我一直安慰自己合陵,他們只是感情好枢赔,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拥知,像睡著了一般踏拜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上低剔,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天速梗,我揣著相機與錄音,去河邊找鬼襟齿。 笑死姻锁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的猜欺。 我是一名探鬼主播位隶,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼开皿!你這毒婦竟也來了涧黄?” 一聲冷哼從身側(cè)響起篮昧,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎笋妥,沒想到半個月后懊昨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡春宣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年酵颁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片月帝。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡躏惋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嫁赏,到底是詐尸還是另有隱情掺炭,我是刑警寧澤胃碾,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響羊瘩,放射性物質(zhì)發(fā)生泄漏潮酒。R本人自食惡果不足惜设预,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一构蹬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧则酝,春花似錦殉簸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至爽雄,卻和暖如春蝠检,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挚瘟。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工叹谁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乘盖。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓焰檩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親订框。 傳聞我的和親對象是個殘疾皇子析苫,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容