C#5.0的異步函數(shù)async\await

如果需要 I/O 綁定(例如從網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)或訪問數(shù)據(jù)庫(kù))鲸沮,則需要利用異步編程。
還可以使用 CPU 綁定代碼(例如執(zhí)行成本高昂的計(jì)算)弯淘,對(duì)編寫異步代碼而言榆综,這是一個(gè)不錯(cuò)的方案妙痹。
C# 擁有語(yǔ)言級(jí)別的異步編程模型,它使你能輕松編寫異步代碼鼻疮,而無(wú)需應(yīng)付回叫或符合支持異步的庫(kù)怯伊。 它遵循基于任務(wù)的異步模式 (TAP)

異步模型的基本概述

對(duì)于 I/O 綁定代碼判沟,當(dāng)你 await 一個(gè)操作耿芹,它將返回 async 方法中的一個(gè) TaskTask<T>

對(duì)于 CPU 綁定代碼挪哄,當(dāng)你 await 一個(gè)操作猩系,它將在后臺(tái)線程通過 Task.Run 方法啟動(dòng)。

await 關(guān)鍵字是點(diǎn)睛之筆中燥,因?yàn)樗鼤和?duì)執(zhí)行 await 的方法的調(diào)用方的控制權(quán)。 這正是 UI 具有響應(yīng)性或服務(wù)具有靈活性的原因塘偎。

I/O 綁定示例:從 Web 服務(wù)下載數(shù)據(jù)

private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI as the request
    // from the web service is happening.
    //
    // The UI thread is now free to perform other work.
    var stringData = await _httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};

CPU 綁定示例:為游戲執(zhí)行計(jì)算
最佳解決方法是啟動(dòng)一個(gè)后臺(tái)線程疗涉,它使用 Task.Run 執(zhí)行工作,并 await 其結(jié)果吟秩。 這可確保在執(zhí)行工作時(shí) UI 能流暢運(yùn)行咱扣。

private DamageResult CalculateDamageDone()
{
    // Code omitted:
    //
    // Does an expensive calculation and returns
    // the result of that calculation.
}


calculateButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI CalculateDamageDone()
    // performs its work.  The UI thread is free to perform other work.
    var damageResult = await Task.Run(() => CalculateDamageDone());
    DisplayDamage(damageResult);
};

內(nèi)部原理

異步操作涉及許多移動(dòng)部分。 若要了解 TaskTask<T> 的內(nèi)部原理涵防,請(qǐng)參閱深入了解異步闹伪。

在 C# 方面,編譯器將代碼轉(zhuǎn)換為狀態(tài)機(jī)壮池,它將跟蹤類似以下內(nèi)容:到達(dá) await 時(shí)暫停執(zhí)行以及后臺(tái)作業(yè)完成時(shí)繼續(xù)執(zhí)行偏瓤。

從理論上講,這是異步的承諾模型的實(shí)現(xiàn)椰憋。

需了解的要點(diǎn)

  • 異步代碼可用于 I/O 綁定和 CPU 綁定代碼厅克,但在每個(gè)方案中有所不同。
  • 異步代碼使用 Task<T>Task橙依,它們是對(duì)后臺(tái)所完成的工作進(jìn)行建模的構(gòu)造证舟。
  • async 關(guān)鍵字將方法轉(zhuǎn)換為異步方法硕旗,這使你能在其正文中使用 await 關(guān)鍵字。
  • 應(yīng)用 await 關(guān)鍵字后女责,它將掛起調(diào)用方法漆枚,并將控制權(quán)返還給調(diào)用方,直到等待的任務(wù)完成抵知。
  • 僅允許在異步方法中使用 await墙基。

識(shí)別 CPU 綁定和 I/O 綁定工作

如果你的工作為 I/O 綁定,請(qǐng)使用 asyncawait(而不使用 Task.Run)辛藻。 不應(yīng)使用任務(wù)并行庫(kù)辅搬。

如果你的工作為 CPU 綁定,并且你重視響應(yīng)能力流妻,請(qǐng)使用 asyncawait卷拘,并在另一個(gè)線程上使用 Task.Run 生成工作。 如果該工作同時(shí)適用于并發(fā)和并行氮墨,則應(yīng)考慮使用任務(wù)并行庫(kù)纺蛆。

此外,應(yīng)始終對(duì)代碼的執(zhí)行進(jìn)行測(cè)量规揪。 例如桥氏,你可能會(huì)遇到這樣的情況:多線程處理時(shí),上下文切換的開銷高于 CPU 綁定工作的開銷猛铅。 每種選擇都有折衷字支,應(yīng)根據(jù)自身情況選擇正確的折衷方案。

等待多個(gè)任務(wù)完成

你可能發(fā)現(xiàn)自己處于需要并行檢索多個(gè)數(shù)據(jù)部分的情況奸忽。 Task API 包含兩種方法(即 Task.WhenAllTask.WhenAny)堕伪,這些方法允許你編寫在多個(gè)后臺(tái)作業(yè)中執(zhí)行非阻止等待的異步代碼。

此示例演示如何為一組 User 捕捉 userId 數(shù)據(jù)

public async Task<User> GetUser(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static Task<IEnumerable<User>> GetUsers(IEnumerable<int> userIds)
{
    var getUserTasks = new List<Task<User>>();

    foreach (int userId in userIds)
    {
        getUserTasks.Add(GetUser(id));
    }

    return await Task.WhenAll(getUserTasks);
}

以下是使用 LINQ 進(jìn)行更簡(jiǎn)潔編寫的另一種方法:

public async Task<User> GetUser(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsers(IEnumerable<int> userIds)
{
    var getUserTasks = userIds.Select(id => GetUser(id));
    return await Task.WhenAll(getUserTasks);
}

盡管它的代碼較少栗菜,但在混合 LINQ 和異步代碼時(shí)需要謹(jǐn)慎操作欠雌。 因?yàn)?LINQ 使用延遲的執(zhí)行,因此異步調(diào)用將不會(huì)像在 foreach() 循環(huán)中那樣立刻發(fā)生疙筹,除非強(qiáng)制所生成的序列通過對(duì) .ToList().ToArray() 的調(diào)用循環(huán)訪問富俄。

重要信息和建議

  • async 方法需在其主體中具有 await 關(guān)鍵字,否則它們將永不暫停而咆!
  • 應(yīng)將Async作為后綴添加到所編寫的每個(gè)異步方法名稱中霍比。
  • async void 應(yīng)僅用于事件處理程序。
    async void 是允許異步事件處理程序工作的唯一方法翘盖,因?yàn)槭录痪哂蟹祷仡愋停ㄒ虼藷o(wú)法利用 TaskTask<T>)桂塞。 其他任何對(duì) async void 的使用都不遵循 TAP 模型,且可能存在一定使用難度馍驯,例如:
    • async void 方法中引發(fā)的異常無(wú)法在該方法外部被捕獲阁危。
    • 十分難以測(cè)試 async void 方法玛痊。
    • 如果調(diào)用方不希望 async void方法是異步方法,則這些方法可能會(huì)產(chǎn)生不好的副作用狂打。
      • 在 LINQ 表達(dá)式中使用異步 lambda 時(shí)請(qǐng)謹(jǐn)慎
        LINQ 中的 Lambda 表達(dá)式使用延遲執(zhí)行擂煞,這意味著代碼可能在你并不希望結(jié)束的時(shí)候停止執(zhí)行。 如果編寫不正確趴乡,將阻塞任務(wù)引入其中時(shí)可能很容易導(dǎo)致死鎖对省。 此外,此類異步代碼嵌套可能會(huì)對(duì)推斷代碼的執(zhí)行帶來更多困難晾捏。 Async 和 LINQ 的功能都十分強(qiáng)大蒿涎,但在結(jié)合使用兩者時(shí)應(yīng)盡可能小心。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惦辛,一起剝皮案震驚了整個(gè)濱河市劳秋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胖齐,老刑警劉巖玻淑,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異呀伙,居然都是意外死亡补履,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門剿另,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箫锤,“玉大人,你說我怎么就攤上這事雨女÷樘” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵戚篙,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我溺职,道長(zhǎng)岔擂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任浪耘,我火速辦了婚禮乱灵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘七冲。我一直安慰自己痛倚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布澜躺。 她就那樣靜靜地躺著蝉稳,像睡著了一般抒蚜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耘戚,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天嗡髓,我揣著相機(jī)與錄音,去河邊找鬼收津。 笑死饿这,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撞秋。 我是一名探鬼主播长捧,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吻贿!你這毒婦竟也來了串结?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤廓八,失蹤者是張志新(化名)和其女友劉穎奉芦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剧蹂,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡声功,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宠叼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片先巴。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖冒冬,靈堂內(nèi)的尸體忽然破棺而出伸蚯,到底是詐尸還是另有隱情,我是刑警寧澤简烤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布剂邮,位于F島的核電站,受9級(jí)特大地震影響横侦,放射性物質(zhì)發(fā)生泄漏挥萌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一枉侧、第九天 我趴在偏房一處隱蔽的房頂上張望引瀑。 院中可真熱鬧,春花似錦榨馁、人聲如沸憨栽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)屑柔。三九已至屡萤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锯蛀,已是汗流浹背灭衷。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旁涤,地道東北人翔曲。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像劈愚,于是被迫代替她去往敵國(guó)和親瞳遍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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