wcf多线程和异步操作
按照操作执行所需的资源类型,我们可以将操作分为cpu绑定型操作和I/O绑定型操作,前者主要是利用cpu进行密集运算,后者大部分操作处理时间花在I/O处理上(比如文件系统、网络资源)。对于I/O绑定型的操作,我们可以充分利用多线程机制,让多个操作在各自的线程上并行执行。
服务调用就是典型的I/O绑定型操作,所以多线程在服务调用具有广泛应用,按照异步操作发生的位置,可以将wcf应用的异步操作分为下面3中类型
1.异步信道调用:客户端通过绑定创建的信道向服务端发送消息,从而实现了对服务的调用。客户端可以通过代理对象异步的调用信道,从而实现异步服务的调用。
2.单向消息交换:客户端的信道通过单向的消息交换模式向服务端发送消息,消息一旦抵达传输层马上返回,从而达到异步服务调用的效果。
3.异步服务实现:服务端在具体实现服务操作的时候,采用异步调用的方式。
一.异步服务的调用
异步服务代理的创建
一般的服务代理继承自ClientBase<TChannel>,默认情况下是不具有异步服务调用的操作。但我们可以通过添加引用的方式创建异步服务代理,并对服务进行异步调用。
在添加服务引用的时候,在弹出的服务引用对话框中点击"高级"按钮,会弹出服务引用设置对话框,然后勾选"生成异步操作"即可
对应生成的服务代理类中就会多出几个方法(下面都以计算的服务为例),以下几个方法在接下来都会介绍到的
[csharp]
public partial class CalculatorClient:ClientBase<ICalculator>,ICalculator
{
public event System.EventHandler<AddCompleteArgs> AddComplete;
public IAsyncResult BeginAdd(double x,double y,AsyncCallback callback,object asuncState);
public double EndAdd(System.IAsyncReuslt result);
public AddAsync(double x,double y);
public AddAsync(double x,double y,object userState);
}
其中Add用于同步服务调用,BeginAdd/EndAdd则用于异步调用。注意应用在BeginAdd方法上的OperationContractAttributes特性的AsyncPattern属性被设置成True
1.通过BeginXxx/EndXxx进行异步服务调用
调用BeginXxx方法让相应的操作在另一个线程执行,方法会立即返回一个IAsyncResult类型的对象,此时我们可以在当前线程执行额外的操作。异步操作执行的结果通过调用EndXxx方法得到,而该方法的参数为调用BeginXxx方法得到的IAsyncResult对象。通过添加计算服务引用生成的代理类型CalculatorClient为例
[csharp]
CalculatorClient proxy=new CalculatorClient();
IAsyncResult asyncResult=proxy.BeginAdd(1,2,null,null);
//其他操作
//Endxx方法可以根据异步操作可能的执行时间来决定调用执行,一旦Endxxx方法被调用后,当前线程会被阻塞直到异步操作之行结束
double result=proxy.EndAdd(asyncResult);
proxy.Close();
Console.WriteLine("x+y={2} when x={0} and y={1}",1,2,result);
注意:一个极端的例子是在调用BeginXxx方法后就直接调用EndXxx方法,虽然在不同的线程中 ,但基本没有起到异步的作用,这里可以用回调的方式来解决这个问题。
2.通过回调的方式进行异步服务调用
我们可以看看上面服务代理类中多出来的几个方法中BeginAdd方法
public IAsyncResult BeginAdd(double x,double y,AsyncCallback callback,object asyncState);
①其中参数类型为AsyncCallback的callback,AsyncCallback是个参数类型为IAsyncResult无返回值的委托,
②参数asyncState可以用于向回调操作传递一些额外的参数,在BeginAdd方法中指定的asyncState可以通过IAsyncResult的AsyncState属性获得;
[csharp]
public delegate void AsyncCallback(IAsyncResult ar);
public interface IAsyncResult
{
object AsyncState {get;}
WaitHandle AsyncWaitHandle {get;}
bool CompletedSynchronously { get;}
bool IsCompleted { get;}
}
下面通过一个匿名方法的形式定义回调操作,由于在回调操作中输出运算结果时需要用到参与的运算数,我们通过BeginAdd方法的asyncState参数实现向回调操作传递数据
,在回调操作中通过IAsyncResult对象的AsyncState获得操作数。
[csharp]
CalculatorClient proxy=new CalculatorClient();
proxy.BeginAdd(1,2,
delegate(IAsyncResult asyncResult)
{
double[] operNums=asyncResult.AsyncState as double[];
double result=proxy.EndAdd(asyncResult);
proxy.Close();
Console.WriteLine("x+y={2} when x={0} and y={1}",operNums[0],operNums[2],result);
},new double[] {1,2});
3.通过事件注册的方式进行异步服务调用
在那个异步服务代理类中还有两个异步执行的AddAsync方法重载和一个AddComplete事件。
public AddAsync(double x,double y);
public AddAsync(double x,double y,object userState);
其中userState参数和BeginAdd方法的asyncState参数具有相同的作用。
[csharp]
public event System.EventHandler<AddCompleteEventArgs> AddComplete;
public class AddCompleteEventArgs:EventArgs
{
public bool Cancelled{get;}
public Exception Error{get;}
public object UserState {get;}
}
事件参数AddCompleteEventArgs定义了3个只读属性,其中Cancelled表示异步操作是否取消,异步操作抛出异常可以通过属性Error得到,UserState返回的是Addsync方法中的userState参数。
由于AddAsync方法是以异步方式执行的,所以调用后会立即返回,如果我们注册了CalculatorClient的AddComplete事件,他将在异步服务调用结束后被触发,所以我们可以按照如下方式调用服务代理Addsync方法
[csharp]
CalculatorClient proxy=new CalculatorClient();
proxy.AddComplete+=delegate(object sender, AddCompleteEventArgs args)
{
double[] operNums=args.UserState as double[];
double result=args.Result;
proxy.Close();
Console.WriteLine("x+y={2} when x={0} and y={1}",operNums[0],operNums[2],result);
};
proxy.AddAsync(1,2,new double[]{1,2});
二.异步服务的实现
上面介绍了如何调用异步服务,但如何服务操作是如何以异步模式实现呢?
异步操作模式
前面提到过在OperationContractAttributes特性的AsyncPattern属性,如果将应用到某个操作的方法上设为True,则意味着对应的操作需要采用异步模式来实现,实际上客户端生成的异步服务代理类实现的契约接口中也包含了这样的异步操作。
异步服务操作是通过BeginXxx/EndXxx两个配对的方法实现的,只需将BeginXx方法的特性将属性AsyncPattern设置为True即可。
三.实例演示 下面通过异步模式来定义一个读取文件的服务
客户端读取:
、
作者:lordbaby