最近幾年治唤,異步編程受到極大關(guān)注棒动,主要是出于兩個(gè)關(guān)鍵原因:首先,它有助于提供更好的用戶體驗(yàn)宾添,因?yàn)椴粫?huì)阻塞 UI 線程船惨,避免了處理結(jié)束前出現(xiàn) UI 界面掛起。其次辞槐,它有助于大幅擴(kuò)展系統(tǒng)掷漱,而且無需添加額外硬件。
但是榄檬,編寫合適的異步代碼來管理線程本身是項(xiàng)乏味的工作卜范。雖然如此,其巨大好處讓許多新舊技術(shù)紛紛開始使用異步編程鹿榜。微軟自發(fā)布了 .NET 4.0以后也對(duì)其投入頗多海雪,隨后在 .NET 4.5中引入了 async 和 await 關(guān)鍵字,使異步編程變得前所未有地簡(jiǎn)單舱殿。
但是奥裸,ASP.NET 中的異步功能自一開始就可以使用,只是從來沒有得到應(yīng)有的重視沪袭。而且湾宙,考慮到 ASP.NET 和 IIS 處理請(qǐng)求的方式,異步體現(xiàn)的優(yōu)勢(shì)可能更明顯。通過異步侠鳄,我們很容易就可以大幅提高 ASP.NET 應(yīng)用程序的擴(kuò)展性埠啃。隨著新的編程結(jié)構(gòu)引入,如 async 和 await 關(guān)鍵字伟恶,我們也應(yīng)該學(xué)會(huì)使用異步編程的強(qiáng)大功能碴开。
在本篇博文中,我們將討論一下 IIS 和 ASP.NET 處理請(qǐng)求的方式博秫,然后看看 ASP.NET 中哪些地方可以使用異步潦牛,最后再討論幾個(gè)最能體現(xiàn)異步優(yōu)勢(shì)的場(chǎng)景。
請(qǐng)求是如何處理的?
每個(gè) ASP.NET 請(qǐng)求都要先通過 IIS挡育,然后再由 ASP.NET 處理程序進(jìn)行最終處理巴碗。 首先IIS 接收請(qǐng)求,初步處理后静盅,發(fā)送給ASP.NET(必須是一個(gè)ASP.NET請(qǐng)求)良价,然后由ASP.NET進(jìn)行實(shí)際處理并生成響應(yīng),之后該響應(yīng)通過IIS發(fā)回給客戶蒿叠。在IIS上明垢,有一些工作進(jìn)程負(fù)責(zé)從隊(duì)列中取出請(qǐng)求,并執(zhí)行IIS 模塊市咽,然后再將該請(qǐng)求發(fā)送到ASP.NET 隊(duì)列痊银。但是,ASP.NET本身不創(chuàng)建任何線程施绎,也沒有處理請(qǐng)求的線程池溯革,而是通過使用CLR 線程池,從中獲取線程來處理請(qǐng)求谷醉。因此致稀,IIS 模塊調(diào)用ThreadPool.QueueUserWorkItem,將請(qǐng)求排入隊(duì)列俱尼,供CLR 工作線程處理抖单。我們都知道,CLR線程池是由CLR管理遇八,并且能夠自動(dòng)調(diào)整(也就是說矛绘,它根據(jù)需要?jiǎng)?chuàng)建和銷毀進(jìn)程)。這里還要記住刃永,創(chuàng)建和銷毀線程是項(xiàng)很繁重的任務(wù)货矮,這就是為什么CLR線程池允許使用同一個(gè)線程處理多個(gè)任務(wù)。下面來看一個(gè)描述請(qǐng)求處理過程的圖示斯够。
在上圖中可以看到囚玫,請(qǐng)求首先由 HTTP.sys接收喧锦,并添加到相應(yīng)內(nèi)核級(jí)應(yīng)用程序池隊(duì)列。然后劫灶,一個(gè)IIS工作線程從隊(duì)列中取出請(qǐng)求裸违,處理后將其傳到ASP.NET 隊(duì)列。注意本昏,該請(qǐng)求如果不是一個(gè)ASP.NET請(qǐng)求,將從 IIS 自動(dòng)返回枪汪。最后涌穆,從CLR線程池中分配一個(gè)線程,負(fù)責(zé)處理該請(qǐng)求雀久。
ASP.NET中異步的使用場(chǎng)景是宿稀?
所有請(qǐng)求大致可以分為兩類:
- CPU Bound 類
- I/O Bound 類
CPU Bound 類請(qǐng)求,需要 CPU 時(shí)間赖捌,而且是在同一進(jìn)程中執(zhí)行祝沸;而 I/O Bound 類請(qǐng)求,本身具有阻塞性越庇,需要依賴其他模塊執(zhí)行 I/O 操作并返回響應(yīng)罩锐。阻塞性請(qǐng)求是提高應(yīng)用程序可伸縮性的主要障礙,而且大多數(shù)web應(yīng)用程序中卤唉,在等待 I/O 操作的過程中浪費(fèi)了大量時(shí)間涩惑。 因此以下場(chǎng)景適合使用異步:
-
I/O Bound 類請(qǐng)求,包括:
a. 數(shù)據(jù)庫(kù)訪問
b. 讀/寫文件
c. Web 服務(wù)調(diào)用
d. 訪問網(wǎng)絡(luò)資源
事件驅(qū)動(dòng)的請(qǐng)求桑驱,比如SignalR
需要從多個(gè)數(shù)據(jù)源獲取數(shù)據(jù)的場(chǎng)景
作為示例竭恬,這里創(chuàng)建一個(gè)簡(jiǎn)單的同步頁面,然后再將它轉(zhuǎn)換成異步頁面熬的。 本示例設(shè)置了1000ms的延遲(以模擬一些繁重的數(shù)據(jù)庫(kù)或web服務(wù)調(diào)用等)痊硕,而且還使用WebClient下載了一個(gè)頁面,如下所示:
protected void Page_Load(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(1000);
WebClient client = new WebClient();
string downloadedContent = client.DownloadString("https://msdn.microsoft.com/en-us/library/hh873175%28v=vs.110%29.aspx");
dvcontainer.InnerHtml = downloadedContent;
}
現(xiàn)在將該頁面轉(zhuǎn)換成異步頁面押框,這里主要涉及三個(gè)步驟:
-
在頁面指令中添加Async = true岔绸,將該頁面轉(zhuǎn)換成異步頁面,如下所示:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="AsyncTest.Home" Async="true" AsyncTimeout="3000" %>
這里還添加了 AsyncTimeout (可選項(xiàng))强戴,請(qǐng)根據(jù)需求選擇亭螟。
2.將此方法轉(zhuǎn)換成異步方法。在這里把Thread.Sleep 與 client.DownloadString 轉(zhuǎn)換成異步方法如下所示:
private async Task AsyncWork()
{
await Task.Delay(1000);
WebClient client = new WebClient();
string downloadedContent = await client.DownloadStringTaskAsync("https://msdn.microsoft.com/en-us/library/hh873175%28v=vs.110%29.aspx ");
dvcontainer.InnerHtml = downloadedContent;
}
3.現(xiàn)在可以直接在 Page_Load (頁面加載)上調(diào)用此方法骑歹,使其異步预烙,如下所示:
protected async void Page_Load(object sender, EventArgs e)
{
await AsyncWork();
}
但是這里的 Page_Load 返回的類型是async void,這種情況無論如何都應(yīng)該避免道媚。我們知道扁掸,Page_Load 是整個(gè)頁面生命周期的一部分翘县,如果我們把它設(shè)置成異步,可能會(huì)出現(xiàn)一些異常情況和事件谴分,比如生命周期已經(jīng)執(zhí)行完畢而頁面加載仍在運(yùn)行锈麸。 因此,強(qiáng)烈建議大家使用 RegisterAsyncTask 方法注冊(cè)異步任務(wù)牺蹄,這些異步任務(wù)會(huì)在生命周期的恰當(dāng)時(shí)間執(zhí)行忘伞,可以避免出現(xiàn)任何問題。
protected void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(AsyncWork));
}
現(xiàn)在沙兰,頁面已經(jīng)轉(zhuǎn)換成了異步頁氓奈,它就不再是一個(gè)阻塞性請(qǐng)求。
筆者在 IIS8.5 上部署了同步頁面和異步頁面鼎天,并使用突發(fā)負(fù)載對(duì)兩者進(jìn)行了測(cè)試舀奶。測(cè)試結(jié)果發(fā)現(xiàn),相同的機(jī)器配置斋射,同步頁面在2-3秒內(nèi)只能提取1000個(gè)請(qǐng)求育勺,而異步頁面能夠?yàn)?200多個(gè)請(qǐng)求提供服務(wù)。此后罗岖,開始收到超時(shí)(Timeout)或服務(wù)器不可用(Server Not Available)的錯(cuò)誤涧至。雖然兩者的平均請(qǐng)求處理時(shí)間沒有多大差別,但是通過異步頁面呀闻,可以處理兩倍以上的請(qǐng)求化借。這足以證明異步編程功能強(qiáng)大,所以應(yīng)該充分利用它的優(yōu)勢(shì)捡多。
ASP.NET中還有幾個(gè)地方也可以引入異步:
- 編寫異步模塊
- 使用IHttpAsyncHandler 或 HttpTaskAsyncHandler 編寫異步HTTP處理程序
- 使用web sockets 或 SignalR
結(jié)論
本篇博文中,我們討論了異步編程蓖康,而且發(fā)現(xiàn),新推出的async 和 await關(guān)鍵字垒手,使異步編程變得十分簡(jiǎn)單蒜焊。我們討論的話題包括 IIS和ASP.NET如何處理請(qǐng)求,以及在哪些場(chǎng)景中異步的作用最明顯科贬。另外泳梆,我們還創(chuàng)建了一個(gè)簡(jiǎn)單示例,討論了異步頁面的優(yōu)勢(shì)榜掌。最后我們還補(bǔ)充了幾個(gè)ASP.NET中可以使用異步的地方优妙。
本文系 OneAPM 工程師編譯呈現(xiàn)。OneAPM 能助您輕松鎖定 .NET 應(yīng)用性能瓶頸憎账,通過強(qiáng)大的 Trace 記錄逐層分析套硼,直至鎖定行級(jí)問題代碼。以用戶角度展示系統(tǒng)響應(yīng)速度胞皱,以地域和瀏覽器維度統(tǒng)計(jì)用戶使用情況邪意。想閱讀更多技術(shù)文章九妈,請(qǐng)?jiān)L問 OneAPM 官方博客。
本文轉(zhuǎn)自 OneAPM 官方博客