為Windows應用建立簡單的非同步呼叫模式

來源:互聯網
上載者:User
簡介

  最近我編寫了很多智能用戶端應用,總結了一些能夠使應用程式在後台調用Web Service時不凍結前台介面的非同步呼叫方法。雖然當前.NET Framework本身已經提供了非同步呼叫的機制,但我發現在Windows應用中這一機制比較難於把握,因為這時你需要正確的控制使用介面執行緒處理。

  在這篇文章中,我將教給您一種在Windows應用程式中實現非同步呼叫Web服務的簡單方法,通過這一方法,您不用再考慮後台線程與前台介面線程的互動關係了。

服務代理

  Visual Studio.NET會產生較好的Web服務代理類,通過它可以非同步使用Web服務,但是這個代理類實現的是.NET Framework本身的非同步呼叫機制,如上所述,這一機制對於Windows應用來說並不十分方便。由於這個原因,我一般不直接使用產生的代理類,而是在中間增加服務代理類。

  服務代理類就是增加了額外功能的類,這些功能可以協助用戶端程式與Web服務進行互動。服務代理類實現了許多有用的功能,包括資料緩衝,安全身份管理,離線操作支援等等。本文中建立的服務代理類比.NET Framework本身的普通代理類實現了更簡便的非同步呼叫模式。

使用介面執行緒

  應用程式從一個建立和系統管理使用者介面的線程起始,這一線程被稱為使用介面執行緒。大多數開發人員本能的會使用使用介面執行緒完成所有的工作,包括進行Web服務調用,遠程對象調用,訪問資料庫等等,大多數使用和效能方面的問題是由這一不恰當的方法引起的。

  問題的本質是你永遠不可能精確的預知訪問Web服務,遠程對象,或者資料庫所需的時間。而且當你在使用介面執行緒中進行這類的調用時,使用者介面就有可能會產生令人惱怒的凍結。

  自然而然的,你會把這一類的調用放置在一個單獨的線程中,但我更進了一步,建議您把所有的非使用者介面工作坊制在一個分離的線程中。我的觀點是,使用介面執行緒只用來系統管理使用者介面,而所有那些你不能保證良好回應時間的對象調用都應該是非同步,無論是進程內的,跨進程的,還是跨電腦的。 無論如何,盡量使使用介面執行緒處理的非同步呼叫模式簡單化,我已經實現了一個與Visual Studio 2005裡某個屬性類別似的簡單非同步呼叫模式。作為開始,我們首先解釋一下當前.NET Framework中非同步呼叫模式的工作原理。

.NET非同步呼叫模式

  系統產生的Web服務代理類的每個Web函數都有一個Begin和一個End方法,每個支援.NET Framework非同步呼叫模式的對象都和這個類似。開始進行非同步呼叫時,用戶端調用Begin方法時就立即響應,或者在建立了訪問Web服務的獨立線程後馬上響應。在這之後的某個時間,當Web服務訪問完成後,用戶端再調用End方法。

  但用戶端如何知道什麼時候調用End方法呢?Begin方法會返回一個IAsyncResult對象,可以協助你跟蹤非同步呼叫的過程,也可以明確的等待後台線程完成,但如果在使用介面執行緒中進行這些工作,會降低整個系統的同步性。更好的方法是,在使用者介面進程中註冊一個回呼函數,當其它工作完成時產生一個自動通知。

讓我們看一段範例代碼,在這段代碼中,我們從一個Web服務中擷取一些客戶資料,這些功能通過Web服務代理類裡的GetCustomerData方法完成。我們可以啟動這個Web服務調用,並且用以下代碼註冊一個回呼函數,用來在使用介面執行緒中產生與應用程式進行互動的功能。

private void SomeUIEvent( object sender, EventArgs e )
{
 // Create a callback delegate so we will
 // be notified when the call has completed.
 AsyncCallback callBack = new
 AsyncCallback( CustomerDataCallback );

 // Start retrieving the customer data.
 _proxy.BeginGetCustomerData( "Joe Bloggs", callBack, null );
}

  Web服務調用最終返回CustomerDataCallback方法,在這個方法中,我們需要調用真正用於擷取客戶資料的Web服務代理類中的End方法,這個方法可以實現如下:

public void CustomerDataCallback( IAsyncResult ar )
{
 // Retrieve the customer data.
 _customerData = _proxy.EndGetCustomerData( ar );
}

  現在,你必須注意,這一方法是被後台背景工作執行緒調用的。如果想在前台介面上使用剛剛獲得的資訊(例如在一個data grid控制項中顯示那些客戶資料),一定不要忘記在使用介面執行緒中進行更新。如果忘了這樣做,就會發生很多莫名其妙的錯誤,而且調試起來還相當的不易。

  那麼我們怎麼切換線程呢?我們可以使用服務代理的方法,所有的控制項都源自這些對象的實現。我們可以實現一個從使用介面執行緒調用的方法,在這個方法內我們可以安全的更新我們的介面。使用Control.Invoke方法,我們必須給使用者更新方法傳遞一個委託,現在,CustomerDataCallback方法的代碼更新如下:

public void CustomerDataCallback( IAsyncResult ar )
{
 // Retrieve the customer data.
 _customerData = _proxy.EndGetCustomerData( ar );

 // Create an EventHandler delegate.
 EventHandler updateUI = new EventHandler( UpdateUI );

 // Invoke the delegate on the UI thread.
 this.Invoke( updateUI, new object[] { null, null } );
}

  UpdateUI方法可以按如下辦法實現:

private void UpdateUI( object sender, EventArgs e )
{
 // Update the user interface.
 _dataGrid.DataSource = _customerData;
}

  這並不是十分非常嚴謹科學,因此可以使這一“兩次跳轉”的複雜問題簡單化起來。關鍵在於非同步方法呼叫的原始調用(以WinForm類為例)用來負責轉換線程,並且需要另一個委託以及Control.Invoke方法。

一個簡化的非同步呼叫模式

  我經常使用一項技術來減少建立非同步呼叫時的複雜度和代碼量,那就是把線程切換和委託的實現放入一個中間類中。這就使得我們從使用者介面類中進行非同步呼叫時,不用再去考慮什麼線程和委託的問題。我把這項技術叫做自動回調。使用這項技術,上面的範例可以進行如下改進:

private void SomeUIEvent( object sender, EventArgs e )
{
 // Start retrieving the customer data.
 _serviceAgent.BeginGetCustomerData( "Joe Bloggs" );
}

  當Web服務訪問完成後,以下的方法就會自動被調用:

private void GetCustomerDataCompleted( DataSet customerData )
{
 // This method will be called on the UI thread.
 // Update the user interface.
 _dataGrid.DataSource = customerData;
}

  回呼函數的名稱由原始非同步呼叫的名稱來決定(因此就不再需要建立和傳遞委託了),並且可以保證被正確的線程所調用(也就不再需要使用Control.Invoke了),這些方法都很簡單並且不容易出錯。

  天下沒有免費的午餐,實現這個簡單模型的神奇代碼是需要我們來編寫的。下面所列的就是被編寫進ServiceAgent類中的這些代碼:

public class ServiceAgent : AutoCallbackServiceAgent
{
 private CustomerWebService _proxy;

 // Declare a delegate to describe the autocallback
 // method signature.
 private delegate void
 GetCustomerDataCompletedCallback( DataSet customerData );

 public ServiceAgent( object callbackTarget )
 : base( callbackTarget )
 {
  // Create the Web service proxy object.
  _proxy = new CustomerWebService();
 }

 public void BeginGetCustomerData( string customerId )
 {
  _proxy.BeginGetCustomerData( customerId,
  new AsyncCallback( GetCustomersCallback ), null );
 }

 private void GetCustomerDataCallback( IAsyncResult ar )
 {
  DataSet customerData = _proxy.EndGetCustomerData( ar );

  InvokeAutoCallback( "GetCustomerDataCompleted",new object[] { customerData },typeof( GetCustomersCompletedCallback ) );
 }
}

  這個範例中服務代理類的代碼是簡單容易的,而且完全可以重用,我們所要做的就是給WinForm類編寫一套類似的通用代碼。我們有效提升了線程管理工作的重要性,並且把它同編寫後台非同步呼叫對象代碼的工作以及編寫前台用戶端代碼的工作分離了開來。 AutoCallbackServiceAgent基類是一個實現了InvokeAutoCallback方法的簡單類,代碼如下:

public class AutoCallbackServiceAgent
{
 private object _callbackTarget;

 public AutoCallbackServiceAgent( object callbackTarget )
 {
  // Store reference to the callback target object.
  _ callbackTarget = callbackTarget;
 }

 protected void InvokeAutoCallback( string methodName,object[] parameters,Type delegateType )
 {
  // Create a delegate of the correct type.
  Delegate autoCallback = Delegate.CreateDelegate( delegateType, _callbackTarget, methodName );

  // If the target is a control, make sure we
  // invoke it on the correct thread.
  Control targetCtrl = _callbackTarget as System.Windows.Forms.Control;
  if ( targetCtrl != null && targetCtrl.InvokeRequired )
  {
   // Invoke the method from the UI thread.
   targetCtrl.Invoke( autoCallback, parameters );
  }
  else
  {
   // Invoke the method from this thread.
   autoCallback.DynamicInvoke( parameters );
  }
 }
}

  以上這些代碼建立了一個回呼函數的委託,並且判斷是在調用線程,還是在使用介面執行緒中調用它。如果調用的目標是一個控制項對象,那麼它就會在需要的時候從使用介面執行緒來調用回呼函數。

  探究這些有趣的細節,如果你仔細的查看代碼,你會發現我們可以通過不在基類中指定自動回調委託來進行簡化。如果我們不需要對回調委託進行簽名,我們就可以幾乎自動化的處理所有的事情,把基礎的服務代理類簡化成只在BeginGetCustomerData方法中實現一行代碼。

  那我們為什麼還要指定這個委託呢?那是因為我們還需要使用Control.Invoke方法。不幸的是.NET Framework的開發人員並沒有為這一方法提供一個MethodInfo對象,而恰恰是它可以使編寫基礎代碼的工作變得簡單許多。

  一個替代的辦法是指定一個標準的委託類型,把它用於所有的回呼函數簽名。舉例來說,我們可以要求所有的自動回呼函數都使用一個方法簽名,這個方法簽名用來維護原始的對象組,並且向用戶端回傳Web服務的參數。委託的聲明方法如下:

public delegate void AutoCallback( object[] parameters );

  使用這個委託我們可以極大地簡化服務代理類的代碼,但是必須在用戶端代碼中把返回的資料轉換成一定的格式。

這樣做值得嗎?

  有必要像上面一樣實現一個服務代理類嗎?這取決於你想多大程度上簡化使用者介面開發人員的工作。編寫一個如上的服務代理類不一定會減少代碼量,但可以使介面開發人員和後台服務開發人員的分工更加明確有效。

  除了提供這種簡單的非同步呼叫模式外,我們還可以往服務代理類中添加更多的有用功能。以後我會繼續在這個思路的基礎上加以擴充,向你展示如何在服務代理類上實現諸如自動本機資料緩衝等進階功能。在服務代理類上實現這些進階功能意味著使用者介面開發人員可以更加輕鬆的完成工作了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.