添加引用服务--高级--选中 生产异步操作
服务端接口操作
[OperationContract]
int Add(int a, int b);
客户端:
引用服务:在引用服务时,左下角点击“高级”按钮,勾选“生成异步操作”即可。
ServiceReference1.Service1Client client= new ServiceReference1.Service1Client();
//int result = client.Add(5, 7); // 同步方法
client.AddCompleted += client_AddCompleted;
client.AddAsync(3, 2); // 异步方法
//另外一种异步调用方式:BeginXXX和EndXXX的方法
private void client_AddCompleted(object sender, AddCompletedEventArgs e)
{
try
{
int result = e.Result;
CalculateClient client = (CalculateClient)sender;
if (client != null)
{
// 释放资源。
client.Close();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
WCF入门(二)——异步操作
WCF是网络编程,既然是网络编程,那么就一般离不开异步操作了。异步操作按照发生源可以分为客户端异步和服务器端异步,本文就分别简单的介绍一下该如何实现。
一、客户端异步
以一个简单的服务为例:
[ServiceContract]
public interface IService1
{
[OperationContract]
string Foo();
}
这个服务不用输入,仅仅返回一个字符串,通常的时候,我们喜欢和调用本地函数方式类似的以调用WCF服务,并且同步等待至远程返回结果。
var client = new WcfClient.Service.Service1Client();
var result = client.Foo();
Console.WriteLine(result);
一般来说,这个并没有什么问题;但是,和调用本地函数一样,如果该函数需要将长时间才能返回结果,则会阻塞线程,如果调用线程是UI线程的话则会导致UI没有响应。特别是像WCF这种远程调用的方式更会由于网络原因等导致耗时较长。
按照本地耗时函数的处理方式,我们可以借助Task来处理这个等待,然后获取返回值后,再通知界面。
var client = new WcfClient.Service.Service1Client();
var result = await Task.Run(() => client.Foo());
Console.WriteLine(result);
这个方式虽然不阻塞界面,但是消耗了一个Task在同步等待,效率不高。实际上,对于这种网络操作,VisualStudio在生成本地客户端代码的时候,就生成了异步方式访问的接口。
直接使用这个异步接口,就可以实现IO异步的方式访问,不用新开启一个Task来同步等待。
var client = new WcfClient.Service.Service1Client();
var result = await client.FooAsync();
Console.WriteLine(result);
关于默认的异步访问接口,也可以在根据服务生成本地代码的时候选择使用传统的BeginFoo,EndFoo的那种形式(由于远不如Task式的异步访问好用,不推荐)。
二、服务器端异步
服务器端异步指的主要是服务的异步实现,
以一个天气预报的WCF服务为例,假如我们要实现一个这样的接口:
public interface IService1
{
string QueryWeather(string city);
}
由于我本地并没有天气预报的数据,要实现一个这样的服务,必须得到气象网站去查询,按照传统的同步方式,则实现如下:
public class Service1 : IService1
{
public string QueryWeather(string city)
{
var queryTask = QueryWeatherFromRemoteSite(city);
queryTask.Wait();
return queryTask.Result;
}
static Task<string> QueryWeatherFromRemoteSite(string city)
{
//省略实现
}
}
很明显,每次一次处理QueryWeather的时候,都得阻塞当前线程至远程查询的完成,极大的浪费了系统资源。需要改成异步的方式实现。
传统的异步实现
在.Net 4.5前,要异步实现QueryWeather函数,必须得把接口声明成如下形式:
[ServiceContract]
public interface IService1
{
[OperationContract(AsyncPattern = true)]
IAsyncResultBeginQueryWeather(string city, AsyncCallback callback, object asyncState);
string EndQueryWeather(IAsyncResult result);
}
可以看到,和同步方式的契约声明不同的是:
-
在接口名称前加了一个Begin
-
设置了AsyncPattern = true
然后就要实现这个接口了,如下是一个简单的示例:
public class Service1 : IService1
{
#region IService1 成员
public IAsyncResult BeginQueryWeather(string city, AsyncCallback callback, object asyncState)
{
return new CompletedAsyncResult<string>(QueryWeatherFromRemoteSite(city));
}
public string EndQueryWeather(IAsyncResult result)
{
return result.AsyncState as string;
}
#endregion
static Task<string> QueryWeatherFromRemoteSite(string city)
{
//这里只是一个桩函数,表示的是一个异步方法
return Task.FromResult("晴");
}
}
class CompletedAsyncResult<T> : IasyncResult
{
Task<T> task;
ManualResetEvent waitHandle = new ManualResetEvent(false);
public CompletedAsyncResult(Task<T> task)
{
this.task = task;
task.ContinueWith(_ => waitHandle.Set());
}
#region IAsyncResult Members
public object AsyncState { get { return task.Result; } }
public WaitHandle AsyncWaitHandle { get { return waitHandle; } }
public bool CompletedSynchronously { get { return true; } }
public bool IsCompleted { get { return task.IsCompleted; } }
#endregion
}
编译运行后,从TestClient上来看,生成的接口并没有带上前面的Begin,从其发布的Wsdl来看,和之前的同步实现没有区别,也就是说:客户端是感知不到服务器端是如何实现的。
更加简单的异步实现:
从上面的实现来看,实现异步操作是要实现两个接口,BeginXXX和EndXXX,这个是非常麻烦的,在.Net 4.5里,我们可以通过async语法糖来简化这一过程。
首先还是来看看接口声明:
[ServiceContract]
public interface IService1
{
[OperationContract]
Task<string> QueryWeatherAsync(string city);
}
和同步方式的契约声明也有两点区别:
-
返回值不是string,而是Task<string>
-
接口名称中加了Async后缀(这个其实不是必要的,但是为了良好的编程习惯,建议加上)。
既然接口就定义除了Task类型的返回值,那么实现就简单了:
public class Service1 : IService1
{
#region IService1 成员
public Task<string> QueryWeatherAsync(string city)
{
return QueryWeatherFromRemoteSite(city);
}
#endregion
static Task<string> QueryWeatherFromRemoteSite(string city)
{
//这里只是一个桩函数,表示的是一个异步方法
return Task.FromResult("晴");
}
}
比前面的BeginXXX的方式来说,简单且简洁了不少。
编译运行后,从TestClient上来看,生成的接口并没有带上结尾的Async,非常人性化。
三、小结:
在WCF架构中,服务器端不感知客户端以同步还是异步的方式来访问,客户端也感知不到服务器端是同步还是异步方式来实现服务的,这个也是一种松耦合的体现。
至于实现异步操作的实现:
-
客户端直接通过代码生成器可以同时生成异步步访问的接口,无需自己编写代码。
-
服务器端可以通过async和Task来简化异步接口的实现。