日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

异步编程 In .NET(转载)

發(fā)布時間:2025/7/25 105 豆豆
生活随笔 收集整理的這篇文章主要介紹了 异步编程 In .NET(转载) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

概述

  在之前寫的一篇關(guān)于async和await的前世今生的文章之后,大家似乎在async和await提高網(wǎng)站處理能力方面還有一些疑問,博客園本身也做了不少的嘗試。今天我們再來回答一下這個問題,同時我們會做一個async和await在WinForm中的嘗試,并且對比在4.5之前的異步編程模式APM/EAP和async/await的區(qū)別,最后我們還會探討在不同線程之間交互的問題。

  IIS存在著處理能力的問題,但是WinForm卻是UI響應(yīng)的問題,并且WinForm的UI線程至始至終都是同一個,所以兩者之間有一定的區(qū)別。有人會問,現(xiàn)在還有人寫WinForm嗎?好吧,它確是一個比較老的東西呢,不如WPF炫,技術(shù)也不如WPF先進,但是從架構(gòu)層面來講,不管是Web,還是WinForm,又或是WPF,Mobile,這些都只是表現(xiàn)層,不是么?現(xiàn)在的大型系統(tǒng)一般桌面客戶端,Web端,手機,平板端都會涉及,這也是為什么會有應(yīng)用層,服務(wù)層的存在。我們在這談?wù)摰腁SP.NET MVC,WinForm,WFP,Android/IOS/WP 都是表現(xiàn)層,在表現(xiàn)層我們應(yīng)該只處理與“表現(xiàn)”相關(guān)的邏輯,任何與業(yè)務(wù)相關(guān)的邏輯應(yīng)該都是放在下層處理的。關(guān)于架構(gòu)的問題,我們后面再慢慢深入,另外別說我沒有提示您,我們今天還會看到.NET中另一個已經(jīng)老去的技術(shù)Web Service。

  還得提示您,文章內(nèi)容有點長,涉及的知識點比較多,所以,我推薦:”先頂后看“ ,先頂后看是21世紀(jì)看長篇的首選之道,是良好溝通的開端,想知道是什么會讓你與眾不同嗎?想知道為什么上海今天會下這么大的雨么?請記住先頂后看,你頂?shù)牟皇俏业奈恼?#xff0c;而是我們冒著大雨還要去上班的可貴精神!先頂后看,你值得擁有!

目錄

  • async/await如何提升IIS響應(yīng)能力
    • 并行處理的步驟
    • 哪些因素影響了我們的響應(yīng)能力
    • async 和 await做了什么?
    • 幾點建議
  • 早期Web Service的異步模式APM
    • 當(dāng)WinForm遇到Web Service
    • WinForm異步調(diào)用Web Service
  • APM異步編程詳解
    • 線程問題
    • 從Delegate開始
    • 再次認(rèn)識APM
  • EAP(Event-Based Asynchroronous Pattern)
    • 線程問題
  • async/await 給WinForm編程帶來了什么?
  • 不同線程之間通訊的問題
    • 萬能的InvokeI
    • SynchronizationContex上下文同步對象
  • 小結(jié)
  • 引用 & 擴展閱讀

async/await如何提升IIS處理能力

  首先響應(yīng)能力并不完全是說我們程序性能的問題,有時候可能你的程序沒有任何問題,而且精心經(jīng)過優(yōu)化,可是響應(yīng)能力還是沒有上去,網(wǎng)站性能分析是一個復(fù)雜的活,有時候只能靠經(jīng)驗和不斷的嘗試才能達到比較好的效果。當(dāng)然我們今天討論的主要是IIS的處理能力,或者也可能說是IIS的性能,但絕非代碼本身的性能。即使async/await能夠提高IIS的處理能力,但是對于用戶來說整個頁面從發(fā)起請求到頁面渲染完成的這些時間,是不會因為我們加了async/await之后產(chǎn)生多大變化的。

  另外異步的ASP.NET并非只有async/await才可以做的,ASP.NET在Web Form時代就已經(jīng)有異步Page了,包括ASP.NET MVC不是也有異步的Controller么?async/await 很新,很酷,但是它也只是在原有一技術(shù)基礎(chǔ)上做了一些改進,讓程序員們寫起異步代碼來更容易了。大家常說微軟喜歡新瓶裝舊酒,至少我們要看到這個新瓶給我們帶來了什么,不管是任何產(chǎn)品,都不可能一開始就很完美,所以不斷的迭代更新,也可以說是一種正確做事的方式。

ASP.NET并行處理的步驟

?  ASP.NET是如何在IIS中工作的一文已經(jīng)很詳細(xì)的介紹了一個請求是如何從客戶端到服務(wù)器的HTTP.SYS最后進入CLR進行處理的(強烈建議不了解這一塊的同學(xué)先看這篇文章,有助于你理解本小節(jié)),但是所有的步驟都是基于一個線程的假設(shè)下進行的。IIS本身就是一個多線程的工作環(huán)境,如果我們從多線程的視角來看會發(fā)生什么變化呢?我們首先來看一下下面這張圖。注意:我們下面的步驟是建立在IIS7.0以后的集成模式基礎(chǔ)之上的。(注:下面這張圖在dudu的提醒之后,重新做了一些搜索工作,做了一些改動,w3dt這一步來自于博客園團隊對問題的不斷探索,詳情可以點這里)

  我們再來梳理一下上面的步驟:

  • 所有的請求最開始是由HTTP.SYS接收的,HTTP.SYS內(nèi)部有一個隊列維護著這些請求,這個隊列的request的數(shù)量大于一定數(shù)量(默認(rèn)是1000)的時候,HTTP.SYS就會直接返回503狀態(tài)(服務(wù)器忙),這是我們的第一個閥門。性能計數(shù)指標(biāo):“Http Service Request Queues\CurrentQueueSize
  • 由w3dt負(fù)責(zé)把請求從HTTP.SYS 的隊列中放到一個對應(yīng)端口的隊列中,據(jù)非官方資料顯示該隊列長度為能為20(該隊列是非公開的,沒有文檔,所以也沒有性能計數(shù)器)。
  • IIS 的IO線程從上一步的隊列中獲取請求,如果是需要ASP.NET處理的,就會轉(zhuǎn)交給CLR 線程池的Worker 線程,IIS的IO線程繼續(xù)返回重復(fù)做該步驟。CLR 線程池的Worker線程數(shù)量是第二個閥門。
  • 當(dāng)CLR中正在被處理的請求數(shù)據(jù)大于一定值(最大并行處理請求數(shù)量,.NET4以后默認(rèn)是5000)的時候,從IO線程過來的請求就不會直接交給Worker線程,而是放到一個進程池級別的一個隊列了,等到這個數(shù)量小于臨界值的時候,才會把它再次交給Worker線程去處理。這是我們的第三個閥門。
  • 上一步中說到的那個進程池級別的隊列有一個長度的限制,可以通過web.config里面的processModel/requestQueueLimit來設(shè)置。這可以說也是一個閥門。當(dāng)正在處理的數(shù)量大于所允許的最大并行處理請求數(shù)量的時候,我們就會得到503了。可以通過性能計數(shù)指標(biāo):“ASP.NET v4.0.30319\Requests Queued” 來查看該隊列的長度。
  • ?哪些因素會控制我們的響應(yīng)能力

      從上面我們提到了幾大閥門中,我們可以得出下面的幾個數(shù)字控制或者說影響著我們的響應(yīng)能力。

  • HTTP.SYS隊列的長度
  • CLR線程池最大Worker線程數(shù)量
  • 最大并行處理請求數(shù)量
  • 進程池級別隊列所允許的長度
  • HTTP.SYS隊列的長度

      這個我覺得不需要額外解釋,默認(rèn)值是1000。這個值取決于我們我們后面IIS IO線程和Worker線程的處理速度,如果它們兩個都處理不了,這個數(shù)字再大也沒有用。因為最后他們會被存儲到進程池級別的隊列中,所以只會造成內(nèi)存的浪費。

    最大Worker線程數(shù)量

      這個值是可以在web.config中進行配置的。

      maxWorkerThreads: CLR中真實處理請求的最大Worker線程數(shù)量
      minWorkerThreads:CLR中真實處理請求的最小Worker線程數(shù)量

      minWorkerThreads的默認(rèn)值是1,合理的加大他們可以避免不必要的線程創(chuàng)建和銷毀工作。

    最大并行處理請求數(shù)量

      進程池級別的隊列給我們的CLR一定的緩沖,這里面要注意的是,這個隊列還沒有進入到CLR,所以它不會占用我們托管環(huán)境的任何資源,也就是把請求卡在了CLR的外面。我們需要在aspnet.config級別進行配置,我們可以在.net fraemwork的安裝目錄下找到它。一般是?C:\Windows\Microsoft.NET\Framework\v4.0.30319 如果你安裝的是4.0的話。

      maxConcurrentRequestPerCPU: 每個CPU所允許的最大并行處理請求數(shù)量,當(dāng)CLR中worker線程正在處理的請求之和大于這個數(shù)時,從IO線程過來的請求就會被放到我們進程池級別的隊列中。
      maxConcurrentThreadsPerCPU: 設(shè)置為0即禁用。
      requestQueue: 進程池級別隊列所允許的長度  

    async和await 做了什么?

      我們終于要切入正題了,拿ASP.NET MVC舉例,如果不采用async的Action,那么毫無疑問,它是在一個Woker線程中執(zhí)行的。當(dāng)我們訪問一些web service,或者讀文件的時候,這個Worker線程就會被阻塞。假設(shè)我們這個Action執(zhí)行時間一共是100ms,其它訪問web service花了80ms,理想情況下一個Worker線程一秒可以響應(yīng)10個請求,假設(shè)我們的maxWorkerThreads是10,那我們一秒內(nèi)總是可響應(yīng)請求就是100。如果說我們想把這個可響應(yīng)請求數(shù)升到200怎么做呢?

      有人會說,這還不簡單,把maxWorkerThreads調(diào)20不就行了么? 其實我們做也沒有什么 問題,確實是可以的,而且也確實能起到作用。那我們?yōu)槭裁催€要大費周章的搞什么 async/await呢?搞得腦子都暈了?async/await給我們解決了什么問題?它可以在我們訪問web service的時候把當(dāng)前的worker線程放走,將它放回線程池,這樣它就可以去處理其它的請求了。等到web service給我們返回結(jié)果了,會再到線程池中隨機拿一個新的woker線程繼續(xù)往下執(zhí)行。也就是說我們減少了那一部分等待的時間,充份利用了線程。

      ? 我們來對比一下使用async/awit和不使用的情況,

      不使用async/await:?20個woker線程1s可以處理200個請求。

      那轉(zhuǎn)換成總的時間的就是 20 * 1000ms = ?20000ms,
      其中等待的時間為 200 * 80ms = 16000ms。
      也就是說使用async/await我們至少節(jié)約了16000ms的時間,這20個worker線程又會再去處理請求,即使按照每個請求100ms的處理時間我們還可以再增加160個請求。而且別忘了100ms是基于同步情況下,包括等待時間在內(nèi)的基礎(chǔ)上得到的,所以實際情況可能還要多,當(dāng)然我們這里沒有算上線程切換的時間,所以實際情況中是有一點差異的,但是應(yīng)該不會很大,因為我們的線程都是基于線程池的操作。
      所有結(jié)果是20個Worker線程不使用異步的情況下,1s能自理200個請求,而使用異步的情況下可以處理360個請求,立馬提升80%呀!采用異步之后,對于同樣的請求數(shù)量,需要的Worker線程數(shù)據(jù)會大大減少50%左右,一個線程至少會在堆上分配1M的內(nèi)存,如果是1000個線程那就是1G的容量,雖然內(nèi)存現(xiàn)在便宜,但是省著總歸是好的嘛,而且更少的線程是可以減少線程池在維護線程時產(chǎn)生的CPU消耗的。另:dudu分享 CLR1秒之內(nèi)只能創(chuàng)建2個線程。

      注意:以上數(shù)據(jù)并非真實測試數(shù)據(jù),真實情況一個request的時間也并非100ms,花費在web service上的時間也并非80ms,僅僅是給大家一個思路:),所以這里面用了async和await之后對響應(yīng)能力有多大的提升和我們原來堵塞在這些IO和網(wǎng)絡(luò)上的時間是有很大的關(guān)系的。

    幾點建議

      看到這里,不知道大家有沒有得到點什么。首先第一點我們要知道的是async/await不是萬能藥,不們不能指望光寫兩個光鍵字就希望性能的提升。要記住,一個CPU在同一時間段內(nèi)是只能執(zhí)行一個線程的。所以這也是為什么async和await建議在IO或者是網(wǎng)絡(luò)操作的時候使用。我們的MVC站點訪問WCF或者Web Service這種場景就非常的適合使用異步來操作。在上面的例子中80ms讀取web service的時間,大部份時間都是不需要cpu操作的,這樣cpu才可以被其它的線程利用,如果不是一個讀取web service的操作,而是一個復(fù)雜計算的操作,那你就等著cpu爆表吧。

      第二點是,除了程序中利用異步,我們上面講到的關(guān)于IIS的配置是很重要的,如果使用了異步,請記得把maxWorkerThreads和maxConcurrentRequestPerCPU的值調(diào)高試試。

    ?早期對Web service的異步編程模式APM

      講完我們高大上的async/await之后,我們來看看這個技術(shù)很老,但是概念確依舊延續(xù)至今的Web Service。 我們這里所說的針對web service的異步編程模式不是指在服務(wù)器端的web service本身,而是指調(diào)用web service的客戶端。大家知道對于web service,我們通過添加web service引用或者.net提供的生成工具就可以生成相應(yīng)的代理類,可以讓我們像調(diào)用本地代碼一樣訪問web service,而所生成的代碼類中對針對每一個web service方法生成3個對應(yīng)的方法,比如說我們的方法名叫DownloadContent,除了這個方法之外還有BeginDownloadContent和EndDownloadContent方法,而這兩個就是我們今天要說的早期的異步編程模式APM(Asynchronous Programming Model)。下面就來看看我們web service中的代碼,注意我們現(xiàn)在的項目都是在.NET Framework3.5下實現(xiàn)的。

    ?PageContent.asmx的代碼

    ?
    1 2 3 4 5 6 7 8 9 public class PageContent : System.Web.Services.WebService { ????[WebMethod] ????public string DownloadContent(string url) ????{ ????????var client = new System.Net.WebClient(); ????????return client.DownloadString(url); ????} }

      注意我們web service中的DownloadContent方法調(diào)用的是WebClient的同步方法,WebClient也有異步方法即:DownloadStringAsync。但是大家要明白,不管服務(wù)器是同步還是異步,對于客戶端來說調(diào)用了你這個web service都是一樣的,就是得等你返回結(jié)果。

      當(dāng)然,我們也可以像MVC里面的代碼一樣,把我們的服務(wù)器端也寫成異步的。那得到好處的是那個托管web service的服務(wù)器,它的處理能力得到提高,就像ASP.NET一樣。如果我們用JavaScript去調(diào)用這個Web Service,那么Ajax(Asynchronous Javascript + XML)就是我們客戶端用到的異步編程技術(shù)。如果是其它的客戶端呢?比如說一個CS的桌面程序?我們需要異步編程么?

    當(dāng)WinForm遇上Web Service

      WinForm不像托管在IIS的ASP.NET網(wǎng)站,會有一個線程池管理著多個線程來處理用戶的請求,換個說法ASP.NET網(wǎng)站生來就是基于多線程的。但是,在WinForm中,如果我們不刻意使用多線程,那至始至終,都只有一個線程,稱之為UI線程。也許在一些小型的系統(tǒng)中WinForm很少涉及到多線程,因為WinForm本身的優(yōu)勢就在它是獨立運行在客戶端的,在性能上和可操作性上都會有很大的優(yōu)勢。所以很多中小型的WinForm系統(tǒng)都是直接就訪問數(shù)據(jù)庫了,并且基本上也只有數(shù)據(jù)的傳輸,什么圖片資源那是很少的,所以等待的時間是很短的,基本不用費什么腦力去考慮什么3秒之內(nèi)必須將頁面顯示到用戶面前這種問題。

      既然WinForm在性能上有這么大的優(yōu)勢,那它還需要異步嗎?

      我們上面說的是中小型的WinForm,如果是大型的系統(tǒng)呢?如果WinForm只是其它的很小一部分,就像我們文章開始說的還有很多其它成千上萬個手機客戶端,Web客戶端,平板客戶端呢?如果客戶端很多導(dǎo)致數(shù)據(jù)庫撐不住怎么辦? 想在中間加一層緩存怎么辦?

      拿一個b2b的網(wǎng)站功能舉例,用戶可以通過網(wǎng)站下單,手機也可以下單,還可以通過電腦的桌面客戶端下單。在下完單之后要完成交易,庫存扣減,發(fā)送訂單確認(rèn)通知等等功能,而不管你的訂單是通過哪個端完成的,這些功能我們都要去做,對嗎?那我們就不能單獨放在WinForm里面了,不然這些代碼在其它的端里面又得全部全新再一一實現(xiàn),同樣的代碼放在不同的地方那可是相當(dāng)危險的,所以就有了我們后來的SOA架構(gòu),把這些功能都抽成服務(wù),每種類型的端都是調(diào)用服務(wù)就可以了。一是可以統(tǒng)一維護這些功能,二是可以很方便的做擴展,去更好的適應(yīng)功能和架構(gòu)上的擴展。比如說像下面這樣的一個系統(tǒng)。

    ?

      在上圖中,Web端雖然也是屬于我們平常說的服務(wù)端(甚至是由多臺服務(wù)器組成的web群集),但是對我們整個系統(tǒng)來說,它也只是一個端而已。對于一個端來說,它本身只處理和用戶交互的問題,其余所有的功能,業(yè)務(wù)都會交給后來臺處理。在我們上面的架構(gòu)中,應(yīng)用層都不會直接參加真正業(yè)務(wù)邏輯相關(guān)的處理,而是放到我們更下層數(shù)據(jù)層去做處理。那么應(yīng)用層主要協(xié)助做一些與用戶交互的一些功能,如果手機短信發(fā)送,郵件發(fā)送等等,并且可以根據(jù)優(yōu)先級選擇是放入隊列中稍候處理還是直接調(diào)用功能服務(wù)立即處理。

      在這樣的一個系統(tǒng)中,我們的Web服務(wù)器也好,Winform端也好都將只是整個系統(tǒng)中的一個終端,它們主要的任何是用戶和后面服務(wù)之間的一個橋梁。涉及到Service的調(diào)用之后,為了給用戶良好的用戶體驗,在WinForm端,我們自然就要考慮異步的問題。?

    WinForm異步調(diào)用Web Service

      有了像VS這樣強大的工具為我們生成代理類,我們在寫調(diào)用Web service的代碼時就可以像調(diào)用本地類庫一樣調(diào)用Web Service了,我們只需要添加一個Web Reference就可以了。

    // Form1.cs的代碼

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private void button1_Click(object sender, EventArgs e) { ????var pageContentService = new localhost.PageContent(); ????pageContentService.BeginDownloadContent( ????????"http://jesse2013.cnblogs.com", ????????new AsyncCallback(DownloadContentCallback), ????????pageContentService); } private void DownloadContentCallback(IAsyncResult result) { ????var pageContentService = (localhost.PageContent)result.AsyncState; ????var msg = pageContentService.EndDownloadContent(result); ????MessageBox.Show(msg); }

      代碼非常的簡單,在執(zhí)行完pageContentService.BeginDownloadContent之后,我們的主線程就返回了。在調(diào)用Web service這段時間內(nèi)我們的UI不會被阻塞,也不會出現(xiàn)“無法響應(yīng)這種情況”,我們依然可以拖動窗體甚至做其它的事情。這就是APM的魔力,但是我們的callback究竟是在哪個線程中執(zhí)行的呢?是線程池中的線程么?咋們接著往下看。

    APM異步編程模式詳解

    線程問題

      接下來我們就是更進一步的了解APM這種模式是如何工作的,但是首先我們要回答上面留下來的問題,這種異步的編程方式有沒有為我們開啟新的線程?讓代碼說話:

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void button1_Click(object sender, EventArgs e) { ????Trace.TraceInformation("Is current thread from thread pool? {0}", Thread.CurrentThread.IsThreadPoolThread ? "Yes" : "No"); ????Trace.TraceInformation("Start calling web service on thread: {0}", Thread.CurrentThread.ManagedThreadId); ????var pageContentService = new localhost.PageContent(); ????pageContentService.BeginDownloadContent( ????????"http://jesse2013.cnblogs.com", ????????new AsyncCallback(DownloadContentCallback), ????????pageContentService); } private void DownloadContentCallback(IAsyncResult result) { ????var pageContentService = (localhost.PageContent)result.AsyncState; ????var msg = pageContentService.EndDownloadContent(result); ????Trace.TraceInformation("Is current thread from thread pool? {0}" , Thread.CurrentThread.IsThreadPoolThread ? "Yes" : "No"); ????Trace.TraceInformation("End calling web service on thread: {0}, the result of the web service is: {1}", ????????Thread.CurrentThread.ManagedThreadId, ????????msg); }

      我們在按鈕點擊的方法和callback方法中分別輸出當(dāng)前線程的ID,以及他們是否屬于線程池的線程,得到的結(jié)果如下:

      Desktop4.0.vshost.exe Information: 0 : Is current thread a background thread? NO
      Desktop4.0.vshost.exe Information: 0 : Is current thread from thread pool? NO
      Desktop4.0.vshost.exe Information: 0 : Start calling web service on thread: 9
      Desktop4.0.vshost.exe Information: 0 : Is current thread a background thread? YES
      Desktop4.0.vshost.exe Information: 0 : Is current thread from thread pool? YES
      Desktop4.0.vshost.exe Information: 0 : End calling web service on thread: 14, the result of the web service is: <!DOCTYPE html>...

      按鈕點擊的方法是由UI直接控制,很明顯它不是一個線程池線程,也不是后臺線程。而我們的callback卻是在一個來自于線程池的后臺線程執(zhí)行的,答案揭曉了,可是這會給我們帶來一個問題,我們上面講了只有UI線程也可以去更新我們的UI控件,也就是說在callback中我們是不能更新UI控件的,那我們?nèi)绾巫尭耈I讓用戶知道反饋呢?答案在后面接曉 :),讓我們先專注于把APM弄清楚。

    從Delegate開始

      其實,APM在.NET3.5以前都被廣泛使用,在WinForm窗體控制中,在一個IO操作的類庫中等等!大家可以很容易的找到搭配了Begin和End的方法,更重要的是只要是有代理的地方,我們都可以使用APM這種模式。我們來看一個很簡單的例子:

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 delegate void EatAsync(string food); private void button2_Click(object sender, EventArgs e) { ????var myAsync = new EatAsync(eat); ????Trace.TraceInformation("Activate eating on thread: {0}", Thread.CurrentThread.ManagedThreadId); ????myAsync.BeginInvoke("icecream", new AsyncCallback(clean), myAsync); } private void eat(string food) { ????Trace.TraceInformation("I am eating.... on thread: {0}", Thread.CurrentThread.ManagedThreadId); } private void clean(IAsyncResult asyncResult) { ????Trace.TraceInformation("I am done eating.... on thread: {0}", Thread.CurrentThread.ManagedThreadId); }

      上面的代碼中,我們通過把eat封裝成一個委托,然后再調(diào)用該委托的BeginInvoke方法實現(xiàn)了異步的執(zhí)行。也就是實際的eat方法不是在主線程中執(zhí)行的,我們可以看輸出的結(jié)果:

      Desktop4.0.vshost.exe Information: 0 : Activate eating on thread: 10
      Desktop4.0.vshost.exe Information: 0 : I am eating.... on thread: 6
      Desktop4.0.vshost.exe Information: 0 : I am done eating.... on thread: 6

      clean是我們傳進去的callback,該方法會在我們的eat方法執(zhí)行完之后被調(diào)用,所以它會和我們eat方法在同一個線程中被調(diào)用。大家如果熟悉代理的話就會知道,代碼實際上會被編譯成一個類,而BeginInvoke和EndInvoke方法正是編譯器為我們自動加進去的方法,我們不用額外做任何事情,這在早期沒有TPL和async/await之前(APM從.NET1.0時代就有了),的確是一個不錯的選擇。

    再次認(rèn)識APM

    了解了Delegate實現(xiàn)的BeginInvoke和EndInvoke之后,我們再來分析一下APM用到的那些對象。 拿我們Web service的代理類來舉例,它為我們生成了以下3個方法:

  • string DownloadContent(string url): 同步方法
  • IAsyncResult BeginDownloadContent(string url, AsyncCallback callback, object asyncState): 異步開始方法
  • EndDownloadContent(IAsyncResult asyncResult):異步結(jié)束方法
  •   在我們調(diào)用EndDownloadContent方法的時候,如果我們的web service調(diào)用還沒有返回,那這個時候就會用阻塞的方式去拿結(jié)果。但是在我們傳到BeginDownloadContent中的callback被調(diào)用的時候,那操作一定是已經(jīng)完成了,也就是說IAsyncResult.IsCompleted = true。而在APM異步編程模式中Begin方法總是返回IAsyncResult這個接口的實現(xiàn)。IAsyncReuslt僅僅包含以下4個屬性:

      WaitHanlde通常作為同步對象的基類,并且可以利用它來阻塞線程,更多信息可以參考MSDN?。 借助于IAsyncResult的幫助,我們就可以通過以下幾種方式去獲取當(dāng)前所執(zhí)行操作的結(jié)果。

  • 輪詢
  • 強制等待
  • 完成通知
  •   完成通知就是們在"WinForm異步調(diào)用WebService"那結(jié)中用到的方法,調(diào)完Begin方法之后,主線程就算完成任務(wù)了。我們也不用監(jiān)控該操作的執(zhí)行情況,當(dāng)該操作執(zhí)行完之后,我們在Begin方法中傳進去的callback就會被調(diào)用了,我們可以在那個方法中調(diào)用End方法去獲取結(jié)果。下面我們再簡單說一下前面兩種方式。

    //輪詢獲取結(jié)果代碼

    ?
    1 2 3 4 5 6 7 8 9 10 11 var pageContentService = new localhost.PageContent(); IAsyncResult asyncResult = pageContentService.BeginDownloadContent( ????"http://jesse2013.cnblogs.com", ????null, ????pageContentService); while (!asyncResult.IsCompleted) { ????Thread.Sleep(100); } var content = pageContentService.EndDownloadContent(asyncResult);

    ?// 強制等待結(jié)果代碼

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var pageContentService = new localhost.PageContent(); IAsyncResult asyncResult = pageContentService.BeginDownloadContent( ????"http://jesse2013.cnblogs.com", ????null, ????pageContentService); // 也可以調(diào)用WaitOne()的無參版本,不限制強制等待時間 if (asyncResult.AsyncWaitHandle.WaitOne(2000)) { ????var content = pageContentService.EndDownloadContent(asyncResult); } else { ????// 2s時間已經(jīng)過了,但是還沒有執(zhí)行完?? }

    EAP(Event-Based Asynchronous Pattern)

      EAP是在.NET2.0推出的另一種過渡的異步編程模型,也是在.NET3.5以后Microsoft支持的一種做法,為什么呢? 如果大家建一個.NET4.0或者更高版本的WinForm項目,再去添加Web Reference就會發(fā)現(xiàn)生成的代理類中已經(jīng)沒有Begin和End方法了,記住在3.5的時候是兩者共存的,你可以選擇任意一種來使用。但是到了.NET4.0以后,EAP成為了你唯一的選擇。(我沒有嘗試過手動生成代理類,有興趣的同學(xué)可以嘗試一下)讓我們來看一下在.NET4下,我們是如何異步調(diào)用Web Service的。

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private void button1_Click(object sender, EventArgs e) { ????var pageContent = new localhost.PageContent(); ????pageContent.DownloadContentAsync("http://jesse2013.cnblogs.com"); ????pageContent.DownloadContentCompleted += pageContent_DownloadContentCompleted; } private void pageContent_DownloadContentCompleted(object sender, localhost.DownloadContentCompletedEventArgs e) { ????if (e.Error == null) ????{ ????????textBox1.Text = e.Result; ????} ????else ????{ ????????// 出錯了 ????} }

    線程問題

      不知道大家還是否記得,在APM模式中,callback是執(zhí)行在另一個線程中,不能隨易的去更新UI。但是如果你仔細(xì)看一下上面的代碼,我們的DownloadContentCompleted事件綁定的方法中直接就更新了UI,把返回的內(nèi)容寫到了一個文本框里面。通過同樣的方法可以發(fā)現(xiàn),在EAP這種異步編程模式下,事件綁定的方法也是在調(diào)用的那個線程中執(zhí)行的。也就是說解決了異步編程的時候UI交互的問題,而且是在同一個線程中執(zhí)行。 看看下面的代碼:

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private void button1_Click(object sender, EventArgs e) { ????Trace.TraceInformation("Call DownloadContentAsync on thread: {0}", Thread.CurrentThread.ManagedThreadId); ????Trace.TraceInformation("Is current from thread pool? : {0}", Thread.CurrentThread.IsThreadPoolThread ? "YES" : "NO"); ????var pageContent = new localhost.PageContent(); ????pageContent.DownloadContentAsync("http://jesse2013.cnblogs.com"); ????pageContent.DownloadContentCompleted += pageContent_DownloadContentCompleted; } private void pageContent_DownloadContentCompleted(object sender, localhost.DownloadContentCompletedEventArgs e) { ????Trace.TraceInformation("Completed DownloadContentAsync on thread: {0}", Thread.CurrentThread.ManagedThreadId); ????Trace.TraceInformation("Is current from thread pool? : {0}", Thread.CurrentThread.IsThreadPoolThread ? "YES" : "NO"); }

      Desktop4.vshost.exe Information: 0 : Call DownloadContentAsync on thread: 10
      Desktop4.vshost.exe Information: 0 : Is current from thread pool? : NO
      Desktop4.vshost.exe Information: 0 : Completed DownloadContentAsync on thread: 10
      Desktop4.vshost.exe Information: 0 : Is current from thread pool? : NO

    async/await 給WinFrom帶來了什么

      如果說async給ASP.NET帶來的是處理能力的提高,那么在WinForm中給程序員帶來的好處則是最大的。我們再也不用因為要實現(xiàn)異步寫回調(diào)或者綁定事件了,省事了,可讀性也提高了。不信你看下面我們將調(diào)用我們那個web service的代碼在.NET4.5下實現(xiàn)一下:

    ?
    1 2 3 4 5 6 7 private async void button2_Click(object sender, EventArgs e) { ????var pageContent = new localhost.PageContentSoapClient(); ????var content = await pageContent.DownloadContentAsync("http://jesse2013.cnblogs.com"); ????textBox1.Text = content.Body.DownloadContentResult; }

      簡單的三行代碼,像寫同步代碼一樣寫異步代碼,我想也許這就是async/await的魔力吧。在await之后,UI線程就可以回去響應(yīng)UI了,在上面的代碼中我們是沒有新線程產(chǎn)生的,和EAP一樣拿到結(jié)果直接就可以對UI操作了。

      async/await似乎真的很好,但是如果我們await后面的代碼執(zhí)行在另外一個線程中會發(fā)生什么事情呢?

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private async void button1_Click(object sender, EventArgs e) { ????label1.Text = "Calculating Sqrt of 5000000"; ????button1.Enabled = false; ????progressBar1.Visible = true; ????double sqrt = await Task<double>.Run(() => ????{ ????????double result = 0; ????????for (int i = 0; i < 50000000; i++) ????????{ ????????????result += Math.Sqrt(i); ????????????progressBar1.Maximum = 50000000; ????????????progressBar1.Value = i; ????????} ????????return result; ????}); ????progressBar1.Visible = false; ????button1.Enabled = true; ????label1.Text = "The sqrt of 50000000 is " + sqrt; }

      我們在界面中放了一個ProgressBar,同時開一個線程去把從1到5000000的平方全部加起來,看起來是一個非常耗時的操作,于是我們用Task.Run開了一個新的線程去執(zhí)行。(注:如果是純運算的操作,多線程操作對性能沒有多大幫助,我們這里主要是想給UI一個進度顯示當(dāng)前進行到哪一步了。)看起來沒有什么問題,我們按F5運行吧!
      Bomb~

      當(dāng)執(zhí)行到這里的時候,程序就崩潰了,告訴我們”無效操作,只能從創(chuàng)建porgressBar的線程訪問它。“ ?這也是我們一開始提到的,在WinForm程序中,只有UI主線程才能對UI進行操作,其它的線程是沒有權(quán)限的。接下來我們就來看看,如果在WinForm中實現(xiàn)非UI線程對UI控制的更新操作。?

    不同線程之間通訊的問題

    萬能的Invoke

      WinForm中絕大多數(shù)的控件包括窗體在內(nèi)都實現(xiàn)了Invoke方法,可以傳入一個Delegate,這個Delegate將會被擁有那個控制的線程所調(diào)用,從而避免了跨線程訪問的問題。

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Trace.TraceInformation("UI Thread : {0}", Thread.CurrentThread.ManagedThreadId); double sqrt = await Task<double>.Run(() => { ????Trace.TraceInformation("Run calculation on thread: {0}", Thread.CurrentThread.ManagedThreadId); ????double result = 0; ????for (int i = 0; i < 50000000; i++) ????{ ????????result += Math.Sqrt(i); ????????progressBar1.Invoke(new Action(() => { ????????????Trace.TraceInformation("Update UI on thread: {0}", Thread.CurrentThread.ManagedThreadId); ????????????progressBar1.Maximum = 50000000; ????????????progressBar1.Value = i; ????????})); ????} ????return result; });

      Desktop.vshost.exe Information: 0 : UI Thread : 9
      Desktop.vshost.exe Information: 0 : Run calculation on thread: 10

      Desktop.vshost.exe Information: 0 : Update UI on thread: 9

      Invoke方法比較簡單,我們就不做過多的研究了,但是我們要考慮到一點,Invoke是WinForm實現(xiàn)的UI跨線程溝通方式,WPF用的卻是Dispatcher,如果是在ASP.NET下跨線程之間的同步又怎么辦呢。為了兼容各種技術(shù)平臺下,跨線程同步的問題,Microsoft在.NET2.0的時候就引入了我們下面的這個對象。

    SynchronizationContext上下文同步對象

    為什么需要SynchronizationContext

      就像我們在WinForm中遇到的問題一樣,有時候我們需要在一個線程中傳遞一些數(shù)據(jù)或者做一些操作到另一個線程。但是在絕大多數(shù)情況下這是不允許的,出于安全因素的考慮,每一個線程都有它獨立的內(nèi)存空間和上下文。因此在.NET2.0,微軟推出了SynchronizationContext。

      它主要的功能之一是為我們提供了一種將一些工作任務(wù)(Delegate)以隊列的方式存儲在一個上下文對象中,然后把這些上下文對象關(guān)聯(lián)到具體的線程上,當(dāng)然有時候多個線程也可以關(guān)聯(lián)到同一個SynchronizationContext對象。獲取當(dāng)前線程的同步上下文對象可以使用SynchronizationContext.Current。同時它還為我們提供以下兩個方法Post和Send,分別是以異步和同步的方法將我們上面說的工作任務(wù)放到我們SynchronizationContext的隊列中。

    SynchronizationContext示例

      還是拿我們上面Invoke中用到的例子舉例,只是這次我們不直接調(diào)用控件的Invoke方法去更新它,而是寫了一個Report的方法專門去更新UI。

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 double sqrt = await Task<double>.Run(() => { ????Trace.TraceInformation("Current thread id is:{0}", Thread.CurrentThread.ManagedThreadId); ????double result = 0; ????for (int i = 0; i < 50000000; i++) ????{ ????????result += Math.Sqrt(i); ????????Report(new Tuple<int, int>(50000000, i)); ????} ????return result; });

      每一次操作完之后我們調(diào)用一下Report方法,把我們總共要算的數(shù)字,以及當(dāng)前正在計算的數(shù)字傳給它就可以了。接下來就看我們的Report方法了。

    ?
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private SynchronizationContext m_SynchronizationContext; private DateTime m_PreviousTime = DateTime.Now; public Form1() { ????InitializeComponent(); ????// 在全局保存當(dāng)前UI線程的SynchronizationContext對象 ????m_SynchronizationContext = SynchronizationContext.Current; } public void Report(Tuple<int, int> value) { ????DateTime now = DateTime.Now; ????if ((now - m_PreviousTime).Milliseconds > 100) ????{ ????????m_SynchronizationContext.Post((obj) => ????????{ ????????????Tuple<int, int> minMax = (Tuple<int, int>)obj; ????????????progressBar1.Maximum = minMax.Item1; ????????????progressBar1.Value = minMax.Item2; ????????}, value); ????????m_PreviousTime = now; ????} }

      整個操作看起來要比Inovke復(fù)雜一點,與Invoke不同的是SynchronizationContext不需要對Control的引用,而Invoke必須先得有那個控件才能調(diào)用它的Invoke方法對它進行操作。

    小結(jié)

      這篇博客內(nèi)容有點長,不知道有多少人可以看到這里:)。最開始我只是想寫寫WinFrom下異步調(diào)用Web Service的一些東西,在一開始這篇文件的題目是”異步編程在WinForm下的實踐“,但是寫著寫著發(fā)現(xiàn)越來越多的迷團沒有解開,其實都是一些老的技術(shù)以前沒有接觸和掌握好,所以所幸就一次性把他們都重新學(xué)習(xí)了一遍,與大家分享。

      我們再來回顧一下文章所涉及到的一些重要的概念:

  • async/await 在ASP.NET做的最大貢獻(早期ASP.NET的異步開發(fā)模式同樣也有這樣的貢獻),是在訪問數(shù)據(jù)庫的時候、訪問遠程IO的時候及時釋放了當(dāng)前的處理性程,可以讓這些線程回到線程池中,從而實現(xiàn)可以去處理其它請求的功能。
  • 異步的ASP.NET開發(fā)能夠在處理能力上帶來多大的提高,取決于我們的程序有多少時間是被阻塞的,也就是那些訪問數(shù)據(jù)庫和遠程Service的時間。
  • 除了將代碼改成異步,我們還需要在IIS上做一些相對的配置來實現(xiàn)最優(yōu)化。
  • 不管是ASP.NET、WinForm還是Mobile、還是平板,在大型系統(tǒng)中都只是一個與用戶交互的端而已,所以不管你現(xiàn)在是做所謂的前端(JavaScript + CSS等),還是所謂的后端(ASP.NET MVC、WCF、Web API 等 ),又或者是比較時髦的移動端(IOS也好,Andrioid也罷,哪怕是不爭氣的WP),都只是整個大型系統(tǒng)中的零星一角而已。當(dāng)然我并不是貶低這些端的價值,正是因為我們專注于不同,努力提高每一個端的用戶體驗,才能讓這些大型系統(tǒng)有露臉的機會。我想說的是,在你對現(xiàn)在技術(shù)取得一定的成就之后,不要停止學(xué)習(xí),因為整個軟件架構(gòu)體系中還有很多很多美妙的東西值得我們?nèi)グl(fā)現(xiàn)。
  • APM和EAP是在async/await之前的兩種不同的異步編程模式。
  • APM如果不阻塞主線程,那么完成通知(回調(diào))就會執(zhí)行在另外一個線程中,從而給我們更新UI帶來一定的問題。
  • EAP的通知事件是在主線程中執(zhí)行的,不會存在UI交互的問題。
  • 最后,我們還學(xué)習(xí)了在Winform下不同線程之間交互的問題,以及SynchronizationContext。
  • APM是.NET下最早的異步編程方法,從.NET1.0以來就有了。在.NET2.0的時候,微軟意識到了APM的回調(diào)函數(shù)中與UI交互的問題,于是帶來了新的EAP。APM與EAP一直共存到.NET3.5,在.NET4.0的時候微軟帶來了TPL,也就是我們所熟知的Task編程,而.NET4.5就是我們大家知道的async/await了,可以看到.NET一直在不停的進步,加上最近不斷的和開源社區(qū)的合作,跨平臺等特性的引入,我們有理由相信.NET會越走越好。
  •   最后,這篇文章從找資料學(xué)習(xí)到寫出來,差不多花了我兩個周未的時間,希望能夠給需要的人或者感興趣想要不斷學(xué)習(xí)的人一點幫助(不管是往前學(xué)習(xí),還是往后學(xué)習(xí))最后還要感謝@田園里面的蟋蟀,在閱讀的時候給我找了一些錯別字!

    引用 & 擴展閱讀

    http://blogs.msdn.com/b/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx
    http://blog.stevensanderson.com/2008/04/05/improve-scalability-in-aspnet-mvc-using-asynchronous-requests
    http://blogs.msdn.com/b/tmarq/archive/2007/07/21/asp-net-thread-usage-on-iis-7-0-and-6-0.aspx?
    http://blogs.msdn.com/b/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx
    http://mohamadhalabi.com/2014/05/08/thread-throttling-in-iis-hosted-wcf-sync-vs-async/
    Pro Asynchronous Programs with .NET by Richard Blewett and Andrew Clymer


    轉(zhuǎn)載于:https://www.cnblogs.com/heroxiaohuihui/p/8056124.html

    總結(jié)

    以上是生活随笔為你收集整理的异步编程 In .NET(转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。