65.9K
CodeProject 正在变化。 阅读更多。
Home

异步通信的编排

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2012年6月1日

CPOL

4分钟阅读

viewsIcon

26802

downloadIcon

275

如何集中化和统一异步执行操作和函数。

Orchestrating the asynchronous communication

引言

如今,异步通信越来越流行和普及。它主要用于提高多处理器环境下的性能并增加应用程序的响应能力。尤其是在引入 Silverlight 后,异步通信已成为必需,因为在该运行时中,所有 Web 请求都以异步方式进行。随着 Windows 8 的出现,这种趋势在一个更基础的层面上得以延续,因为许多系统调用在那里都是异步进行的。

实现异步通信需要开发者具备额外的技能,因为他们会在一定程度上失去对操作顺序的控制,这反过来又会导致分散但仍然相关的源代码片段,这与某些面向对象的范式相悖。

本文介绍了一些简单的方法,可以极大地简化处理异步通信。相关的库提供了几种工具,具有以下优点:

  • 统一 4 个区域的操作顺序:MainOnSuccessOnErrorOnFinally
  • 组合操作序列的代码
  • 通过使用泛型实现返回类型和参数的类型安全
  • 自动重用函数的返回值
  • 简化错误和异常处理
  • 将异步通信的核心集中在一个地方,以便于分析、记录等。

异步操作

要异步执行操作,该库提供了 AsyncAction 类,该类又提供了各种用于启动它的实用方法。最详细的签名定义如下:

public static void Start(
  Invocable main,
  Invocable onSuccess,
  InvocableAction1Base<Exception> onError,
  Invocable onFinally,
  AsyncContext context )

参数 mainonSuccessonErroronFinally 代表上述操作顺序图中的代码块。如果 Main 执行无误,则启动 OnSuccess 块。否则,将生成的 Exception 作为参数传递给 OnError。对于 AsyncAction 的使用,必须提供一个 Main 操作。AsyncContext 封装了执行上下文,并将在后面进行描述。

在下面的示例中,除了 Main 操作之外,还使用了 OnSuccess 操作。

// ----------------------------------------------------------------------
public void DeleteAllCustomersMinimal()
{
  AsyncAction.Start(
    new InvocableAction0( Services.DeleteAllCustomers ),
    new InvocableAction0( () => Customers.Clear() ) );
} // DeleteAllCustomersMinimal

操作有多种变体,最多可接受 4 个参数:InvocableAction0InvocableAction4

Asynchronous Action Classes

给定此继承层次结构,可以根据需要单独定制对相应操作的调用。以下示例演示了如何通过使用 InvocableAction2OnError 操作添加一个额外的 Customer 参数。

// ----------------------------------------------------------------------
public void DeleteCustomer( Customer customer )
{
  AsyncAction.Start(
    // main
    new InvocableAction1<int>( 
      Services.DeleteCustomer, customer.Id ),
    // on success
    new InvocableAction1<Customer>( 
      c => Customers.Remove( c ), customer ),
    // on error
    new InvocableAction2<Exception, Customer>( 
      ( e, c ) => ShowError( e + " - " + c ), customer ),
    // on finally
    new InvocableAction1<Customer>( 
      c => Debug.WriteLine( "DeleteCustomer: " + c ) ) );
} // DeleteCustomer

异步函数

可以通过 AsyncFunction 类中 start 方法的各种变体来启动异步函数。所有执行参数中最详细的签名定义如下:

public static void Start(
  InvocableFunction<TResult> main,
  InvocableAction1Base<TResult> onSuccess,
  InvocableAction1Base<Exception> onError,
  Invocable onFinally,
  AsyncContext context )

参数 mainonSuccessonErroronFinally 代表上述操作顺序图中的代码块。与 AsyncAction 不同,参数 Main 的类型为 InvocableFunction,因此会返回一个结果。如果 Main 执行无误,则启动 OnSuccess 块。Main 函数的返回值将作为第一个参数传递给 OnSuccess 操作。如果 Main 函数执行过程中发生异常,该异常将作为参数传递给 OnError 操作。否则,将生成的 Exception 作为参数传递给 OnError。对于 AsyncFunction 的使用,必须提供一个 Main 函数和一个相应的 OnSuccess 操作。AsyncContext 封装了执行上下文,并将在后面进行描述。

以下示例演示了如何将 Main 函数的结果传递给 OnSuccess 操作。

// ----------------------------------------------------------------------
public void GetCustomerSalesVolumeMinimal( Customer customer )
{
  AsyncFunction<double>.Start(
    // main
    new InvocableFunction1<int, double>(
      Services.GetCustomerSalesVolume, customer.Id ),
    // on success
    new InvocableAction2<double, Customer>(
      ( sv, c ) => c.SalesVolume = sv, customer ) );
} // GetCustomerSalesVolumeMinimal

AsyncFunctionMain 参数的合适参数是最多可接受 4 个参数的函数:InvocableFunction0InvocableFunction4

Asynchronous Function Classes

执行上下文

异步通信的核心是 AsyncContext 类,该类由 AsyncActionAsyncFunction 使用。它管理以下方面:


Dispatcher  

用于实际运行异步操作或函数的 Dispatcher。默认情况下,它将使用当前线程的 Dispatcher

线程  

通过 ShouldInvokeOnDispatcherThread 属性,可以决定 Dispatcher 是在当前 Thread 中运行,还是通过 ThreadPool 在单独的 Thread 中运行。

Invoke  

实现了实际执行异步操作或函数的中央操作序列。

错误处理

为了正确处理异步 Web 通信(Silverlight)中的服务器端错误,还需要进一步的步骤。有必要封装发生在服务器上的错误,并将它们(通过 WCF)传输到客户端。

本文避免了对故障处理的描述,因为它与主题不完全相关且超出了范围。但是,有很多关于如何实现这一点的示例,例如在 Catch and Handle WCF Service Exceptions in Silverlight 中。

应用示例

Asynchronous communication demo application

捆绑的 WPF、Silverlight 和 Windows Phone 示例应用程序演示了如何异步加载客户信息列表。

// ----------------------------------------------------------------------
private void LoadCustomers()
{
  AsyncFunction<CustomerCollection>.Start(
    // main
    new InvocableFunction0<CustomerCollection>( services.LoadCustomers ),
    // on success
    new InvocableAction1<CustomerCollection>( OnCustomersLoaded ),
    // on error
    new InvocableAction1<Exception>( OnCustomerLoadError ),
    // on finally
    new InvocableAction1<string>( OnFinished, "LoadCustomers" ) );
} // LoadCustomers

// ----------------------------------------------------------------------
private void OnCustomersLoaded( CustomerCollection customers )
{
  CustomerList.ItemsSource = customers;
  if ( customers != null && customers.Count > 0 )
  {
    CustomerList.SelectedIndex = 0;
  }
  AddToHistory( string.Format( "{0} customers loaded", customers != null ? customers.Count : 0 ) );
} // OnCustomersLoaded

// ----------------------------------------------------------------------
private void OnCustomerLoadError( Exception exception )
{
  AddToHistory( string.Format( "error: {0}", exception.Message ) );
} // OnCustomerLoadError

// ----------------------------------------------------------------------
private void OnFinished( string action )
{
  AddToHistory( string.Format( "'{0}' finished", action ) );
} // OnFinished

在加载完客户后,它们会显示在一个列表中。选择列表中的一个客户将在详细信息部分显示其数据。此外,该客户的销售额将从外部服务异步获取。

private void UpdateCustomerInfo( Customer customer )
{
  // static customer data
  CustomerId.Text = customer.Id.ToString( CultureInfo.InvariantCulture );
  CustomerFirstName.Text = customer.FirstName;
  CustomerLastName.Text = customer.LastName;
 
  // sales volume
  AsyncFunction<double>.Start(
    // main
    new InvocableFunction1<int, double>( services.GetCustomerSalesVolume, customer.Id ),
    // on success
    new InvocableAction2<double, Customer>(
      ( salesVolume, cust ) =>
      {
        cust.SalesVolume = salesVolume;
        CustomerSalesVoume.Text = salesVolume.ToString( "C" );
        AddToHistory( string.Format( "customer {0} sales volume: {1:C}", cust, salesVolume ) );
      },
      customer ),
    // on error
    new InvocableAction2<Exception, Customer>(
      ( e, cust ) =>
      {
        CustomerSalesVoume.Text = string.Empty;
        AddToHistory( string.Format( "error get sales volume: {0}", cust ) );
      },
      customer ),
    // on finally
    new InvocableAction1<string>(
      action => AddToHistory( string.Format( "'{0}' finished", action ) ),
      "GetCustomerSalesVolume" ) );
} // UpdateCustomerInfo

致谢

特别感谢 Leon Poyyayil 的设计和初步实现,以及他在本项目开发中的支持和贡献。

历史

  • 2012 年 6 月 1 日 - v1.0.0.0
    • 首次公开发布
© . All rights reserved.