asp.net如何?非同步編程

來源:互聯網
上載者:User

asp教程.net如何?非同步編程
對於很多人來說,非同步就是使用後台線程運行耗時的操作。在有些時候這是對的,而在我們日常大部分情境中卻不對。

比如現在我們有這麼一個需求:使用httpwebrequest請求某個指定uri的內容,然後輸出在介面上的文本域中。同步代碼很容易編寫:

   1: private void btndownload_click(object sender,eventargs e)   2: {   3:     var request = httpwebrequest.create("http://www.sina.com.cn");   4:     var response = request.getresponse();   5:     var stream = response.getresponsestream();   6:     using(streamreader reader = new streamreader(stream))   7:     {   8:         var content = reader.readtoend();   9:         this.txtcontent.text = content;  10:     }  11: }


是吧,很簡單。但是正如上一篇文章所說,這個簡短的程式體驗會非常差。特別是在uri所指向的資源非常大,網路非常慢的情況下,在點擊下載按鈕到獲得結果這段時間介面會假死。

哦,這個時候你想起了非同步。回憶上篇文章的示意圖。我們發現只要我們將耗時的操作放到另外一個線程上執行就可以了,這樣我們的ui線程可以繼續響應使用者的操作。

使用獨立的線程實現非同步
如是你寫下了下面的代碼:

 

  1: private void btndownload_click(object sender,eventargs e)   2: {   3:     var downloadthread = new thread(download);   4:     downloadthread.start();   5: }   6:     7: private void download()   8: {   9:     var request = httpwebrequest.create("http://www.sina.com.cn");  10:     var response = request.getresponse();  11:     var stream = response.getresponsestream();  12:     using(streamreader reader = new streamreader(stream))  13:     {  14:         var content = reader.readtoend();  15:         this.txtcontent.text = content;  16:     }  17: }

然後,f5運行。很不幸,這裡出現了異常:我們不能在一個非ui線程上更新ui的屬性(更詳細的討論參見我的這篇文章:winform二三事(三)control.invoke&control.begininvoke)。我們暫時忽略這個異常(在release模式下是不會出現的,但這是不推薦的做法)。

哦,你寫完上面的代碼後發現ui不再阻塞了。心裡想,非同步也不過如此嘛。過了一會兒你突然想起,你好像在哪本書裡看到過說盡量不要自己聲明thread,而應用使用線程池。如是你搜尋了一下msdn,將上面的代碼改成下面這個樣子:

  

1: private void btndownload_click(object sender,eventargs e)   2: {   3:     threadpool.queueuserworkitem(download);       4: }   5:     6: private void download()   7: {   8:     var request = httpwebrequest.create("http://www.sina.com.cn");   9:     var response = request.getresponse();  10:     var stream = response.getresponsestream();  11:     using(streamreader reader = new streamreader(stream))  12:     {  13:         var content = reader.readtoend();  14:         this.txtcontent.text = content;  15:     }  16: }
 

嗯,很容易完成了。你都有點佩服自己了,這麼短的時間居然連線程池這麼“進階的技術”都給使用上了。就在你沾沾自喜的時候,你的一個同事走過來說:你這種實現方式是非常低效的,這裡要進行的耗時操作屬於io操作,不是計算密集型,可以不分配線程給它(雖然不算準確,但如果不深究的話就這麼認為吧)。

你的同事說的是對的。對於io操作(比如讀寫磁碟,網路傳輸,資料庫教程查詢等),我們是不需要佔用一個thread來執行的。現代的磁碟等裝置,都可以與cpu同時工作,在磁碟尋道讀取這段時間cpu可以幹其他的事情,當讀取完畢之後通過中斷再讓cpu參與進來。所以上面的代碼,雖然構建了響應靈敏的介面,但是卻建立了一個什麼也不乾的線程(當進行網路請求這段時間內,該線程會被一直阻塞)。所以,如果你要進行非同步時首先要考慮,耗時的操作屬於計算密集型還是io密集型,不同的操作需要採用不同的策略。對於計算密集型的操作你是可以採用上面的方法的:比如你要進行很複雜的方程的求解。是採用專門的線程還是使用線程池,也要看你的操作的關鍵程度。

這個時候你又在思考,不讓我使用線程,又要讓我實現非同步。這該怎麼辦呢?微軟早就幫你想到了這點,在.net framework中,幾乎所有進行io操作的方法幾乎都提供了同步版本和非同步版本,而且微軟為了簡化非同步使用難度還定義了兩種非同步編程模式:

classic async pattern
這種方式就是提供兩個方法實現非同步編程:比如system.io.stream的read方法:

public int read(byte[] buffer,int offset,int count);

它還提供了兩個方法實現非同步讀取:

public iasyncresult beginread(byte[] buffer, int offset,int count,asynccallback callback);

public int endread(iasyncresult asyncresult);

以begin開頭的方法發起非同步作業,begin開頭的方法裡還會接收一個asynccallback類型的回調,該方法會在非同步作業完成後執行。然後我們可以通過調用endread獲得非同步作業的結果。關於這種模式更詳細的細節我不在這裡多闡述,感興趣的同學可以閱讀《clr via c#》26、27章,以及《.net設計規範》裡對非同步模式的描述。在這裡我會使用這種模式重新實現上面的程式碼片段:

   1: private static readonly int buffer_length = 1024;   2:     3: private void btndownload_click(object sender,eventargs e)   4: {   5:     var request = httpwebrequest.create("http://www.sina.com.cn");   6:     request.begingetresponse((ar) => {   7:         var response = request.endrequest(ar);   8:         var stream = response.getresponsestream();   9:         readhelper(stream,0);  10:     },null);  11: }  12:    13: private void readhelper(stream stream,int offset)  14: {  15:     var buffer = new byte[buffer_length];  16:     stream.beginread(buffer,offset,buffer_length,(ar) =>{  17:         var actualread = stream.endread(ar);  18:           19:         if(actualread == buffer_length)  20:         {  21:             var partialcontent = encoding.default.getstring(buffer);  22:             update(partialcontent);  23:             readhelper(stream,offset+buffer_length);  24:         }  25:         else  26:         {  27:             var latestcontent = encoding.default.getstring(buffer,0,actualread);  28:             update(latestcontent);  29:             stream.close();  30:         }  31:     },null);  32: }  33:    34: private void update(string content)  35: {  36:     this.begininvoke(new action(()=>{this.txtcontent.text += content;}));  37: }
感謝lambda運算式,讓我少些了很多方法聲明,也少引入了很多執行個體成員。不過上面的代碼還是非常難以讀懂,原本簡簡單單的同步代碼被改寫成了分段式的,而且我們再也無法使用using了,所以需要顯示的寫stream.close()。哦,My Code還沒有進行異常處理,這令我非常頭痛。實際上要寫出一個健壯的非同步代碼是非常困難的,而且非常難以調試。但是,上面的代碼不僅僅能建立響應靈敏的介面,還能更高效的利用線程。在這種非同步模式中,beginxxx方法會返回一個iasyncresult對象,在進行非同步編程時也非常有效,關於它的更詳細資料你可以閱讀我的這篇文章:winform二三事(二)非同步作業。

除此之外,因為我們在這裡不能使用while等迴圈,我們想要從stream裡讀取完整的內容並不是一件容易事兒,我們必須將很好的迴圈結果替換成遞迴調用:readhelper。

event-based async pattern(eap)
.net framework除了提供上面這種編程模式外,還提供了基於事件的非同步編程模式。比如webclient的很多方法就提供了非同步版本,比如downloadstring方法。

同步版本:

public string downloadstring(string url);

非同步版本:

public void downloadstringasync(string url);

public event downloadstringcompleteeventhandler downloadstringcomplete;

(在這裡請注意,這兩種非同步編程模式以及未來要介紹的async ctp中的tap方法的命名,參數的傳遞都是有一定規則的,弄清楚這些規則在進行非同步編程時會事半功倍)

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.