I坐标

博客园 首页 新随笔 联系 订阅 管理
本文为原文的节选翻译,并非全文翻译。

原文信息

标题: Creating a Simplified Asynchronous Call Pattern for Windows Forms Applications
站点: MSDN
作者: David Hill Microsoft Corporation
地址: http://msdn.microsoft.com/smartclient/windowsforms/default.aspx?pull=/library/en-us/dnwinforms/html/sacp_winforms.asp

代码下载

示例代码下载

简介

虽然Microsoft在.Net Framework中提供了一个通用的使用异步调用的模式,但是我发现有时候这个模式有一点笨重,
因为我们必须确认UI线程是否被正确地使用。
在这篇文章中,我将会阐述如何实现一个简单地异步调用模式,这个模式允许你在Windows Forms 应用程序中调用
Web services而无需担心后台线程对UI线程的影响。

服务代理

    VS.NET可以生成一个很好的WebService代理类,利用该代理类可以异步调用WebService,但
是这个代理类是.NET Framework异步调用模式的实现,下面我将会说到,这个模式在WindowsForms应用程序中使用
相当不方便。
    因此,我通常不使用这个直接生成的代理类,而是使用一个中间代理的类Service Agent。
    Service agents是一组提供了更多的和Web Service交互的功能的类。它们能实现许多有用的功能。例如数据缓存,
安全证书管理,离线操作支持等等。相对于上面提到的功能而言,本文中的Service Agent类只提供一个相当简单的异
步调用模式。
    虽然我可以将附加的功能直接添加到VS.NET生成的代理类中,但是我还是喜欢不去修改那些直接生成的代码,这样
做可以避免当我刷新Web Reference的时候,由于VS.NET的不完善的功能而丢失我自己的代码。所以,我的Service 
Agent直接调用生成的Web Service代理类去实现调用Web Service。
   
    

UI线程

    一个应用程序由一个用来创建和管理用户界面的线程开始。这个线程被称为UI线程。开发者很自
然的都是使用这个线程去处理任何事情,包括调用Web Service, 远程调用对象,处理数据库操作等等。这样做会导致
性能和可用性的问题。这些问题就是你不能预知调用Web Service、远程对象以及操作数据库需要多长时间。并且如果
你是在UI线程中开始这样的调用,那么你的UI很有可能被锁定而不能和用户交互。
    自然的,你会想在另一个线程中开始这样的调用,但是我会更深一步你可以在另一个线程中做所有和UI无关的操作。
我坚定的相信,UI线程只能被用于管理UI,那些你无法保证可以及时响应的调用都应该异步调用。
    

.NET 异步调用模式

    一个支持.Net Framework异步调用模板的对象,例如一个Web Service代理类,为每一个被暴露
的Web Method都有一个Begin和一个End方法。为了调用一个异步调用,一个客户端调用Begin方法,该方法会立即返回
结果,或者创建一个独立的线程去完成实际的Web Service调用。在调用Begin方法后的某一个时间点上,客户端会调用
End方法结束Web Service的调用。
    那么客户端什么时候调用End方法?Begin方法返回一个IAsyncResult对象,你能利用这个对象去跟踪异步调用的过程,
同时你还能利用它去明确的结束后台线程的运行,但是在UI进程中开始这样的调用很可能会在调用点无法返回正常流程。
所以,一个很好的办法就是在UI线程中注册一个Callback,这样就可以在后台线程完成时自动获得通知。
    让我们看看一些示例代码。在示例代码中,假设我们想从Web Service异步的获取一些客户的数据。我们有一个Web 
Service代理对象,并且该对象有一个名为GetCustomerData的Web方法。在代码中我们能调用Web Service并且注册了
一个callback方法,这样我们就能确认在UI线程中触发这个调用而不会影响UI和用户的交互。
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 ); }
CustomerDataCallback就是Web Service调用完成后的返回点。在这个方法中,我们需要调用Web Service的代理对象 的End方法去获取Customer数据。其大致代码如下所示:
public void CustomerDataCallback( IAsyncResult ar ) {
// Retrieve the customer data.
_customerData = _proxy.EndGetCustomerData( ar ); }
现在,我们需要把注意力放在在后台工作线程调用。那么我们如何切换线程呢? 现在我们使用Control.Invoke方法。为了使用这个方法,我们需要传递一个代理到UI线程的方法中。让我们假设我们现 在需要在获取数据的同时,更新显示的数据的控件。 为了实现这个功能,我们的代码大致如下:
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; }
虽然这不是精密科学,但是我还是发觉没有必要如上面的代码那样做“双重发射”:异步方法的原始调用者为了能响应其 它的线程需要一个代理和Control.Invoke方法。

一个简单的异步调用模式

我经常用来减少复杂度和代码量的方法就是把异步调用中的线程切换和代理封装到一个intermediate 
class中。我们先来看看代码。
private void SomeUIEvent( object sender, EventArgs e ) {
// Start retrieving the customer data.
_serviceAgent.BeginGetCustomerData( "Joe Bloggs" ); }
private void GetCustomerDataCompleted( DataSet customerData ) {
// This method will be called on the UI thread.
// Update the user interface.
_dataGrid.DataSource = customerData; }
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 ) ); } }
如上面的代码,我在UI线程的事件中调用Web Service的Begin方法。那么当Web Service调用结束之后,就调用 GetCustomerDataCompleted方法,这样就不会构造和传递代理了。同时也不再需要Control.Invoke方法。这样我们 也就能很明确的知道在那个线程中开始我们的调用。 让我们把目光放在ServiceAgent上,这个一个很简单的类。它直接调用已经生成的代理的Begin方法开始一个 调用,用End方法结束一个调用。当然,很重要的就是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 ); } } }
上面的代理创建了一个代理去回调以及决定是在UI线程还是在调用线程触发这个代理。假如触发对象是Control 的子类,那么就在UI线程触发这个代理。
posted on 2004-06-29 16:12  I坐标  阅读(1994)  评论(1)    收藏  举报