2002-09-11 14:46
Web Service 的异步调用
在网络中为了保证数据的实时性,需要对数据进行异步操作。Java Web Service和J2EE中的异步操作通过java消息机制来完成,消息机制是非常完善的技术了。而Microsoft的Web Service的异步功能是怎样完成的呢?怎样才能达到java的境地呢?当然,Microsoft有自己的一套。
众所周知,Web Service是靠SOAP协议进行数据传输的。而SOAP是基于XML技术之上的。SOAP协议是连接客户和服务器的桥梁。而SOAP协议本身没有异步功能,需要在客户端实现异步调用。我们以一个简单的Web Service的例子来说明这一点。
一、MathService.asmx
<%@ WebService Language="C#" Class="MathService" %>
using System;
using System.Web.Services;
[WebService]
public class MathService : WebService {
[WebMethod]
public float Add(float a, float b)
{
return a + b;
}
[WebMethod]
public double Subtract(double a, double b)
{
return a - b;
}
[WebMethod]
public float Multiply(float a, float b)
{
return a * b;
}
[WebMethod]
public float Divide(float a, float b)
{
if (b==0) return -1;
return a / b;
}
}
这是个实现了加,减,乘,除的Web Service,任何客户端程序都可以调用它。下面我们用wsdl(微软公司提供)工具产生一个MathService.asmx 的客户代理程序:wsdl /n:MyMath http://localhost/mathservice.asmx (假设MathService.asmx放在IIS服务器的根目录) ,产生一个MathService.cs代理程序,默认是SOAP协议。
二、MathService.cs:
namespace MyMath{
using System.Diagnostics;
using System.Xml.Serialization;
using System;
using System.Web.Services.Protocols;
using System.ComponentModel;
using System.Web.Services;
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(Name="MathServiceSoap", Namespace="http://tempuri.org/")]
public class MathService : System.Web.Services.Protocols.SoapHttpClientProtocol {
public MathService() {
this.Url = "http://localhost/mathservice.asmx";
}
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Add", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public System.Single Add(System.Single a, System.Single b) {
object[] results = this.Invoke("Add", new object[] {
a,
b});
return ((System.Single)(results[0]));
}
public System.IAsyncResult BeginAdd(System.Single a, System.Single b, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("Add", new object[] {
a,
b}, callback, asyncState);
}
/// <remarks/>
public System.Single EndAdd(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((System.Single)(results[0]));
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Subtract", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public System.Double Subtract(System.Double a, System.Double b) {
object[] results = this.Invoke("Subtract", new object[] {
a,
b});
return ((System.Double)(results[0]));
}
/// <remarks/>
public System.IAsyncResult BeginSubtract(System.Double a, System.Double b, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("Subtract", new object[] {
a,
b}, callback, asyncState);
}
/// <remarks/>
public System.Double EndSubtract(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((System.Double)(results[0]));
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Multiply", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public System.Single Multiply(System.Single a, System.Single b) {
object[] results = this.Invoke("Multiply", new object[] {
a,
b});
return ((System.Single)(results[0]));
}
/// <remarks/>
public System.IAsyncResult BeginMultiply(System.Single a, System.Single b, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("Multiply", new object[] {
a,
b}, callback, asyncState);
}
/// <remarks/>
public System.Single EndMultiply(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((System.Single)(results[0]));
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Divide", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public System.Single Divide(System.Single a, System.Single b) {
object[] results = this.Invoke("Divide", new object[] {
a,
b});
return ((System.Single)(results[0]));
}
/// <remarks/>
public System.IAsyncResult BeginDivide(System.Single a, System.Single b, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("Divide", new object[] {
a,
b}, callback, asyncState);
}
/// <remarks/>
public System.Single EndDivide(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((System.Single)(results[0]));
}
}
}
之后我们用csc /t:library MathService.cs编译并产生一个MathService.dll.
现在我们可以写任何的客户程序去调用服务器上的MathService.asmx。
如:WinForm, C#,ASPX等。
下面我们写一个test.cs去测试异步调用:
三、test.cs:
using System;
public class test{
public static void Main(){
MyMath.MathService math = new MyMath.MathService();
IAsyncResult result1 = math.BeginAdd(10,20,null,null);
Object result=math.EndAdd(result1);
Console.WriteLine("result =========="+result);
}
}
我们看到它是先调用代理MathService.cs中的BeginAdd方法,然后状态信息保存在IasyncResult中,直到调用了EndAdd方法才返回调用的确切值。本例是远端调用MathService.asmx中的Add方法。
那Microsoft到底怎样实现客户端的异步呢?设计模式又是怎样的呢?
异步模式所提供的革新之一就是调用方确定特定调用是否应是异步的。 对于被调用的对象,没有必要执行附加的编程来用于支持其客户端的异步行为;在该模式中异步委托提供此功能。公共语言运行库处理调用方和被调用的对象视图之间的差异。被调用的对象可以选择显式支持异步行为,这或者是因为它可以比一般结构更为有效地实现异步行为,或者是因为它想只支持其调用方的异步行为。但是,建议这种被调用的对象遵循公开异步操作的异步设计模式。
类型安全是异步模式的另一项革新。尤其对于异步委托,针对 .NET 框架和公共语言运行库的语言编译器可令映射到规则 Invoke 方法的开始和结束操作(例如,BeginInvoke 和 EndInvoke)的方法签名是类型安全的。这是十分重要的,因为编译器为异步委托将同步调用拆分成开始和结束操作,使其能够只传递有效参数。
在此模式中所蕴含的基本想法如下所示:
1.调用方确定特定调用是否应是异步的。
2. 对于被调用的对象,没有必要由其客户端执行附加的编程来用于支持异步行为。公共语言运行库结构应该能够处理调用方和被调用的对象视图之间的差异。
3. 被调用的对象可以选择显式支持异步行为,这或者是因为它可以比一般结构更为有效地实现异步行为,或者是因为它想只支持其调用方的异步行为。但是,建议这种被调用的对象遵循公开异步操作的异步设计模式。
4. 编译器为 BeginInvoke 和 EndInvoke 以及异步委托生成类型安全方法签名。
5. .NET 框架提供支持异步编程模型所需的服务。此类服务的部分列表示例是:
(1)同步基元,例如监视器和阅读器编写器锁定。
(2)线程和线程池。
(3)同步构造,例如支持等候对象的容器。
(4)向基础结构片(例如 IMessage 对象和线程池)公开。
该模式将一个同步调用拆分成各构成部分:开始操作、结束操作和结果对象。考虑以下示例,在其中可能要用大量时间来完成 Factorize 方法。
public class PrimeFactorizer
{
public bool Factorize(int factorizableNum, ref int primefactor1, ref int primefactor2)
{
// Determine whether factorizableNum is prime.
// If is prime, return true. Otherwise, return false.
// If is prime, place factors in primefactor1 and primefactor2.
}
}
如果遵循异步模式,则类库编写器添加 BeginFactorize 和 EndFactorize方法,这两个方法将同步操作拆分成两个异步操作:
public class PrimeFactorizer
{
public bool Factorize(
int factorizableNum,
ref int primefactor1,
ref int primefactor2)
{
// Determine whether factorizableNum is prime.
// if is prime, return true; otherwise return false.
// if is prime palce factors in primefactor1 and primefactor2
}
public IAsyncResult BeginFactorize(
int factorizableNum,
ref int primefactor1,
ref int primefactor2,
AsyncCallback callback,
Object state)
{
// Begin the factorizing asynchronously, and return a result object,
}
public bool EndFactorize(
ref int primefactor1,
ref int primefactor2,
IAsyncResult asyncResult
)
{
// End (or complete) the factorizing, and
// return the results,
// and obtain the prime factors.
}
}
服务器将异步操作拆分成两个逻辑部分:采用来自客户端的输入并调用异步操作的部分,向客户端提供异步操作结果的部分。
除了异步操作所需的输入外,第一部分还采用在完成异步操作时后要被调用的 AsyncCallback 委托。第一部分返回一个可等待的对象,该对象实现客户端使用的 IAsyncResult 接口来确定异步操作的状态。
服务器还利用它返回到客户端的可等待的对象来维护与异步操作关联的任何状态。通过提供可等待的对象,客户端使用第二部分获取异步操作的结果。
可用于客户端来启动异步操作的选项有:
在开始异步调用时提供回调委托。
public class Driver1
{
public PrimeFactorizer primeFactorizer;
public void Results(IAsyncResult
asyncResult)
{
int primefactor1=0;
int primefactor2=0;
bool prime = primeFactorizer.EndFactorize(
ref primefactor1,
ref primefactor2,
asyncResult);
}
public void Work()
{
int factorizableNum=1000589023,
int primefactor1=0;
int primefactor2=0;
Object state = new Object();
primeFactorizer = new PrimeFactorizer();
AsyncCallback callback = new Callback(this.Results);
IAsyncResult asyncResult = primeFactorizer.BeginFactorize(
factorizableNum,
ref primefactor1,
ref primefactor2,
callback,
state);
}
}
在开始异步调用时不提供回调委托。
public class Driver2
{
public static void Work()
{
int factorizableNum=1000589023,
int primefactor1=0;
int primefactor2=0;
Object state = new Object();
PrimeFactorizer primeFactorizer = new PrimeFactorizer();
AsyncCallback callback = new Callback(this.Results);
IAsyncResult asyncResult = primeFactorizer.BeginFactorize(
factorizableNum,
ref primefactor1,
ref primefactor2,
callback,
state);
bool prime = primeFactorizer.EndFactorize(
ref primefactor1,
ref primefactor2,
asyncResult);
}
}
我们以.Net的一个例子来说明这一点:
AsyncDelegate2.cs
using System;
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
public class Wak
{
public int Pat(int i)
{
Console.WriteLine("Hash: {0} Wak Pat", Thread.CurrentThread.GetHashCode());
return i*2;
}
};
public delegate int WakPatDelegate(int i);// 异步调用的委派.
public class Simple
{
public static void SomeMethod(IAsyncResult ar)
{
// Obtain value from AsyncState object
int value = Convert.ToInt32(ar.AsyncState);
// Obtain results via EndInvoke
int result = ((WakPatDelegate)((AsyncResult)ar).AsyncDelegate ).EndInvoke(ar);
Console.WriteLine("Simple.SomeMethod (AsyncCallback): Result
of {0} in Wak.Pak is {1} ",value, result);
}
public static void Main(String[] args)
{
Console.WriteLine("Thread Simple Context Sample");
Console.WriteLine("");
Console.WriteLine("Make an instance of a context-bound type Wak");
Wak oWak = new Wak();
int value=0;
int result=0;
Console.WriteLine("Make a sync call on the object");
value = 10;
result = oWak.Pat(value);
Console.WriteLine("Result of {0} in Wak.Pak is {1} ",value, result);
Console.WriteLine("Make single Async call on Context-bound object");
WakPatDelegate wpD1 = new WakPatDelegate(oWak.Pat);
value = 20;
IAsyncResult ar1 = wpD1.BeginInvoke(value,null,null);
ar1.AsyncWaitHandle.WaitOne();
result = wpD1.EndInvoke(ar1);
Console.WriteLine("Result of {0} in Wak.Pak is {1} ",value, result);
Console.WriteLine("Make single Async call on Context-bound object - use AsyncCallback and StateObject");
WakPatDelegate wpD2 = new WakPatDelegate(oWak.Pat);
value = 30;
IAsyncResult ar2 = wpD2.BeginInvoke(
value,
new AsyncCallback(Simple.SomeMethod),
value
);
Console.WriteLine("Make multiple Async calls on Context-bound object");
int asyncCalls = 5;
IAsyncResult[] ars = new IAsyncResult[asyncCalls];
WaitHandle[] whs = new WaitHandle[asyncCalls];
int[] values = new int[asyncCalls];
WakPatDelegate wpD3 = new WakPatDelegate(oWak.Pat);
for (int i=0; i < asyncCalls; i++)
{
values = i;
ars = wpD3.BeginInvoke(values,null,null);
whs = ars.AsyncWaitHandle;
}
WaitHandle.WaitAll(whs,1000, false);
for (int i=0; i < asyncCalls; i++)
{
result = wpD3.EndInvoke(ars);
Console.WriteLine("Result of {0} in Wak.Pak is {1} ",values, result);
}
Console.WriteLine("");
Console.WriteLine("Done");
}
}
如果异步调用成功,将显示:
Thread Simple Context Sample
Make an instance of a context-bound type Wak
Make a sync call on the object
Hash: 3 Wak Pat
Result of 10 in Wak.Pak is 20
Make single Async call on Context-bound object
Hash: 16 Wak Pat
Result of 20 in Wak.Pak is 40
Make single Async call on Context-bound object - use AsyncCallback and StateObje
ct
Hash: 16 Wak Pat
Make multiple Async calls on Context-bound object
Simple.SomeMethod (AsyncCallback): Result of 30 in Wak.Pak is 60
Hash: 16 Wak Pat
Hash: 16 Wak Pat
Hash: 16 Wak Pat
Hash: 16 Wak Pat
Hash: 16 Wak Pat
Result of 0 in Wak.Pak is 0
Result of 1 in Wak.Pak is 2
Result of 2 in Wak.Pak is 4
Result of 3 in Wak.Pak is 6
Result of 4 in Wak.Pak is 8
Done