一步一步学Remoting之五:异步操作
如果你还不知道什么是异步也不要紧,我们还是来看实例,通过实例来理解才是最深刻的。
在Remoting中,我们可以使用以下几种异步的方式:
1、普通异步
2、回调异步
3、单向异步
一个一个来说,首先我们这么修改我们的远程对象:
这个方法传入2个参数,返回2个参数和表示方法执行成功,方法需要time毫秒的执行时间,这是一个长时间的方法。
如果方法我们通过异步远程调用,这里需要注意到这个方法输出的行是在服务器端输出的而不是客户端。因此,为了测试简单,我们还是在采用本地对象,在实现异步前我们先来看看同步的调用方法,为什么说这是一种阻塞?因为我们调用了方法主线程就在等待了,看看测试:
假设method方法是主线程的方法,需要3秒的时间:
好了,现在开始运行程序:
用了4秒,说明在我们的方法开始以后本地就一直在等待了,总共用去的时间=本地方法+远程方法,对于长时间方法调用这显然不科学!我们需要改进:
1、普通异步:
首先在main方法前面加上委托,签名和返回类型和异步方法一致。
main方法里面这么写:
来看一下执行结果:
现在总共执行时间接近于主线程的执行时间了,等于是调用方法基本不占用时间。
分析一下代码:Iar.AsyncWaitHandle.WaitOne(); 是阻塞等待异步方法完成,在这里这段代码是不会被执行的,因为主方法完成的时候,异步方法早就IsCompleted了,如果我们修改一下代码:IAsyncResult Iar=md.BeginInvoke(1,2,5000,null,null);
可以看到,主线程方法结束后就在等待异步方法完成了,总共用去了接近于异步方法的时间:5秒。
在实际的运用中,主线程往往需要得到异步方法的结果,也就是接近于上述的情况,我们在主线程做了少量时间的工作以后最终要是要WaitOne去等待异步操作返回的结果,才能继续主线程操作。看第二个图可以发现,异步操作仅仅用了1秒,但是要等待3秒的主线程方法完成后再返回结果,这还是不科学啊。因此,我们要使用委托回调的异步技术。
2、回调异步:
如果你还不知道什么是异步也不要紧,我们还是来看实例,通过实例来理解才是最深刻的。
在Remoting中,我们可以使用以下几种异步的方式:
1、普通异步
2、回调异步
3、单向异步
一个一个来说,首先我们这么修改我们的远程对象:
public int ALongTimeMethod(int a,int b,int time)
{
Console.WriteLine("异步方法开始");
System.Threading.Thread.Sleep(time);
Console.WriteLine("异步方法结束");
return a+b;
}
{
Console.WriteLine("异步方法开始");
System.Threading.Thread.Sleep(time);
Console.WriteLine("异步方法结束");
return a+b;
}
这个方法传入2个参数,返回2个参数和表示方法执行成功,方法需要time毫秒的执行时间,这是一个长时间的方法。
如果方法我们通过异步远程调用,这里需要注意到这个方法输出的行是在服务器端输出的而不是客户端。因此,为了测试简单,我们还是在采用本地对象,在实现异步前我们先来看看同步的调用方法,为什么说这是一种阻塞?因为我们调用了方法主线程就在等待了,看看测试:
DateTime dt=DateTime.Now;
RemoteObject.MyObject app=new RemoteObject.MyObject();
Console.WriteLine(app.ALongTimeMethod(1,2,1000));
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
RemoteObject.MyObject app=new RemoteObject.MyObject();
Console.WriteLine(app.ALongTimeMethod(1,2,1000));
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
假设method方法是主线程的方法,需要3秒的时间:
private static void Method()
{
Console.WriteLine("主线程方法开始");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
{
Console.WriteLine("主线程方法开始");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
好了,现在开始运行程序:
用了4秒,说明在我们的方法开始以后本地就一直在等待了,总共用去的时间=本地方法+远程方法,对于长时间方法调用这显然不科学!我们需要改进:
1、普通异步:
首先在main方法前面加上委托,签名和返回类型和异步方法一致。
private delegate int MyDelegate(int a,int b,int time);
main方法里面这么写:
DateTime dt=DateTime.Now;
RemoteObject.MyObject app=new RemoteObject.MyObject();
MyDelegate md=new MyDelegate(app.ALongTimeMethod);
IAsyncResult Iar=md.BeginInvoke(1,2,1000,null,null);
Method();
if(!Iar.IsCompleted)
{
Iar.AsyncWaitHandle.WaitOne();
}
else
{
Console.WriteLine("结果是"+md.EndInvoke(Iar));
}
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
RemoteObject.MyObject app=new RemoteObject.MyObject();
MyDelegate md=new MyDelegate(app.ALongTimeMethod);
IAsyncResult Iar=md.BeginInvoke(1,2,1000,null,null);
Method();
if(!Iar.IsCompleted)
{
Iar.AsyncWaitHandle.WaitOne();
}
else
{
Console.WriteLine("结果是"+md.EndInvoke(Iar));
}
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
来看一下执行结果:
现在总共执行时间接近于主线程的执行时间了,等于是调用方法基本不占用时间。
分析一下代码:Iar.AsyncWaitHandle.WaitOne(); 是阻塞等待异步方法完成,在这里这段代码是不会被执行的,因为主方法完成的时候,异步方法早就IsCompleted了,如果我们修改一下代码:IAsyncResult Iar=md.BeginInvoke(1,2,5000,null,null);
可以看到,主线程方法结束后就在等待异步方法完成了,总共用去了接近于异步方法的时间:5秒。
在实际的运用中,主线程往往需要得到异步方法的结果,也就是接近于上述的情况,我们在主线程做了少量时间的工作以后最终要是要WaitOne去等待异步操作返回的结果,才能继续主线程操作。看第二个图可以发现,异步操作仅仅用了1秒,但是要等待3秒的主线程方法完成后再返回结果,这还是不科学啊。因此,我们要使用委托回调的异步技术。
2、回调异步:
class MyClient
{
private delegate int MyDelegate(int a,int b,int time);
private static MyDelegate md;
[STAThread]
static void Main(string[] args)
{
DateTime dt=DateTime.Now;
//RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
RemoteObject.MyObject app=new RemoteObject.MyObject();
md=new MyDelegate(app.ALongTimeMethod);
AsyncCallback ac=new AsyncCallback(MyClient.CallBack);
IAsyncResult Iar=md.BeginInvoke(1,2,1000,ac,null);
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
}
public static void CallBack(IAsyncResult Iar)
{
if(Iar.IsCompleted)
{
Console.WriteLine("结果是"+md.EndInvoke(Iar));
}
}
private static void Method()
{
Console.WriteLine("主线程方法开始");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
}
{
private delegate int MyDelegate(int a,int b,int time);
private static MyDelegate md;
[STAThread]
static void Main(string[] args)
{
DateTime dt=DateTime.Now;
//RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
RemoteObject.MyObject app=new RemoteObject.MyObject();
md=new MyDelegate(app.ALongTimeMethod);
AsyncCallback ac=new AsyncCallback(MyClient.CallBack);
IAsyncResult Iar=md.BeginInvoke(1,2,1000,ac,null);
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
}
public static void CallBack(IAsyncResult Iar)
{
if(Iar.IsCompleted)
{
Console.WriteLine("结果是"+md.EndInvoke(Iar));
}
}
private static void Method()
{
Console.WriteLine("主线程方法开始");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
}
可以看到我上面的注释行,去掉远程调用的注释,对下面的本地调用注释,编译后启动服务端,再启动客户端就是远程调用了。
异步调用结束,立即就能显示出结果,如果开启远程方法的话,可以看的更加清晰:
客户端:主线程方法开始-》服务端:异步方法开始-》服务端:异步方法结束-》客户端:结果是3-》客户端:主线程方法结束-》客户端:用了3.03125秒。
3、单向异步就是像同步调用方法那样调用方法,方法却是异步完成的,但是不能获得方法的返回值而且不能像同步方法那样取得所调用方法的异常信息!对于不需要返回信息的长时间方法,我们可以放手让它去干就行了:
远程对象:
using System;
using System.Runtime.Remoting.Messaging;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
[OneWay]
public void ALongTimeMethodOneWay(int time)
{
Console.WriteLine("异步方法开始");
System.Threading.Thread.Sleep(time);
Console.WriteLine("异步方法结束");
}
}
}
using System.Runtime.Remoting.Messaging;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
[OneWay]
public void ALongTimeMethodOneWay(int time)
{
Console.WriteLine("异步方法开始");
System.Threading.Thread.Sleep(time);
Console.WriteLine("异步方法结束");
}
}
}
[OneWay]属性是Remoting.Messaging的一部分,别忘记using,下面看看客户端代码:
using System;
namespace RemoteClient
{
class MyClient
{
[STAThread]
static void Main(string[] args)
{
DateTime dt=DateTime.Now;
RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
//RemoteObject.MyObject app=new RemoteObject.MyObject();
app.ALongTimeMethodOneWay(1000);
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
}
private static void Method()
{
Console.WriteLine("主线程方法开始");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
}
}
namespace RemoteClient
{
class MyClient
{
[STAThread]
static void Main(string[] args)
{
DateTime dt=DateTime.Now;
RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
//RemoteObject.MyObject app=new RemoteObject.MyObject();
app.ALongTimeMethodOneWay(1000);
Method();
Console.WriteLine("用了"+((TimeSpan)(DateTime.Now-dt)).TotalSeconds+"秒");
Console.ReadLine();
}
private static void Method()
{
Console.WriteLine("主线程方法开始");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("主线程方法结束");
}
}
}
这次我们仅仅只能在远程调试,我们先让异步方法去做,然后就放心的做主线程的事情,其他不管了。
运行结果我描述一下:
客户端:主线程方法开始-》服务端:异步方法开始-》服务端:异步方法结束-》客户端:主线程方法结束-》客户端:用了3.8秒。
上面说的三种方法,只是异步编程的一部分,具体怎么异步调用远程方法要结合实际的例子,看是否需要用到方法的返回和主线程方法的运行时间与远程方法运行时间等结合起来考虑,比如上述的WaitHandle也可以用轮询来实现:
while(Iar.IsCompleted==false) System.Threading.Thread.Sleep(10);总的来说远程对象的异步操作和本地对象的异步操作是非常接近。