程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 更多關於編程 >> 全面解析C#中的異步編程

全面解析C#中的異步編程

編輯:更多關於編程

       當我們處理一些長線的調用時,經常會導致界面停止響應或者IIS線程占用過多等問題,這個時候我們需要更多的是用異步編程來修正這些問題,但是通常都是說起來容易做起來難,誠然異步編程相對於同步編程來說,它是一種完全不同的編程思想,對於習慣了同步編程的開發者來說,在開發過程中難度更大,可控性不強是它的特點。

      在.NET Framework5.0種,微軟為我們系統了新的語言特性,讓我們使用異步編程就像使用同步編程一樣相近和簡單,本文中將會解釋以前版本的Framework中基於回調道德異步編程模型的一些限制以及新型的API如果讓我們簡單的做到同樣的開發任務。

      為什麼要異步

      一直以來,使用遠程資源的編程都是一個容易造成困惑的問題,不同於“本地資源”,遠程資源的訪問總會有很多意外的情況,網絡環境的不穩定機器服務端的故障,會造成很多程序員完全不可控的問題,所以這也就要求程序員需要更多的去保護遠程資源的調用,管理調用的取消、超市、線程的等待以及處理線程長時間沒響應的情況等。而在.NET中我們通常忽略了這些挑戰,事實上我們會有多種不用的模式來處理異步編程,比如在處理IO密集型操作或者高延遲的操作時候不組測線程,多數情況我們擁有同步和異步兩個方法來做這件事。可是問題在於當前的這些模式非常容易引起混亂和代碼錯誤,或者開發人員會放棄然後使用阻塞的方式去開發。

      而在如今的.NET中,提供了非常接近於同步編程的編程體驗,不需要開發人員再去處理只會在異步編程中出現的很多情況,異步調用將會是清晰的且不透明的,而且易於和同步的代碼進行組合使用。

      過去糟糕的體驗

      最好的理解這種問題的方式是我們最常見的一種情況:用戶界面只擁有一個線程所有的工作都運行在這個線程上,客戶端程序不能對用戶的鼠標時間做出反應,這很可能是因為應用程序正在被一個耗時的操作所阻塞,這可能是因為線程在等待一個網絡ID或者在做一個CPU密集型的計算,此時用戶界面不能獲得運行時間,程序一直處於繁忙的狀態,這是一個非常差的用戶體驗。

      很多年來,解決這種問題的方法都是做異步花的調用,不要等待響應,盡快的返回請求,讓其他事件可以同時執行,只是當請求有了最終反饋的時候通知應用程序讓客戶代碼可以執行指定的代碼。

      而問題在於:異步代碼完全毀掉了代碼流程,回調代理解釋了之後如何工作,但是怎麼在一個while循環裡等待?一個if語句?一個try塊或者一個using塊?怎麼去解釋“接下來做什麼”?

      看下面的一個例子:

      public int SumPageSizes(IList uris)

      {

      int total = 0;

      foreach (var uri in uris)

      {

      txtStatus.Text = string.Format("Found {0} bytes...", total);

      var data = new WebClient().DownloadData(uri);

      total += data.Length;

      }

      txtStatus.Text = string.Format("Found {0} bytes total", total);

      return total;

      }

      這個方法從一個uri列表裡下載文件,統計他們的大小並且同時更新狀態信息,很明顯這個方法不屬於UI線程因為它需要花費非常長的時間來完成,這樣它會完全的掛起UI,但是我們又希望UI能被持續的更新,怎麼做呢?

      我們可以創建一個後台編程,讓它持續的給UI線程發送數據來讓UI來更新自身,這個看起來是很浪費的,因為這個線程把大多時間花在等下和下載上,但是有的時候,這正是我們需要做的。在這個例子中,WebClient提供了一個異步版本的DownloadData方法—DownloadDataAsync,它會立即返回,然後在DownloadDataCompleted後觸發一個事件,這允許用戶寫一個異步版本的方法分割所要做的事,調用立即返回並完成接下來的UI線程上的調用,從而不再阻塞UI線程。下面是第一次嘗試:

      public void SumpageSizesAsync(IList uris)

      {

      SumPageSizesAsyncHelper(uris.GetEnumerator(), 0);

      }

      public void SumPageSizesAsyncHelper(IEnumerator enumerator, int total)

      {

      if (enumerator.MoveNext())

      {

      txtStatus.Text = string.Format("Found {0} bytes...", total);

      var client = new WebClient();

      client.DownloadDataCompleted += (sender,e)=>{

      SumPageSizesAsyncHelper(enumerator, total + e.Result.Length);

      };

      client.DownloadDataAsync(enumerator.Current);

      }

      else

      {

      txtStatus.Text = string.Format("Found {0} bytes total", total);

      }

      }

      然後這依然是糟糕的,我們破壞了一個整潔的foreach循環並且手動獲得了一個enumerator,每一個調用都創建了一個事件回調。代碼用遞歸取代了循環,這種代碼你應該都不敢直視了吧。不要著急,還沒有完 。

      原始的代碼返回了一個總數並且顯示它,新的一步版本在統計還沒有完成之前返回給調用者。我們怎麼樣才可以得到一個結果返回給調用者,答案是:調用者必須支持一個回掉,我們可以在統計完成之後調用它。

      然而異常怎麼辦?原始的代碼並沒有關注異常,它會一直傳遞給調用者,在異步版本中,我們必須擴展回掉來讓異常來傳播,在異常發生時,我們不得不明確的讓它傳播。

     

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved