Remoting之非同步操作模式

javaprogramers發表於2006-05-14
如果你還不知道什麼是非同步也不要緊,我們還是來看例項,通過例項來理解才是最深刻的。
在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;
        }

這個方法傳入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();

假設method方法是主執行緒的方法,需要3秒的時間:
private static void Method()
        {
            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();

來看一下執行結果:


現在總共執行時間接近於主執行緒的執行時間了,等於是呼叫方法基本不佔用時間。
分析一下程式碼: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(
"主執行緒方法結束");
        }
    }

可以看到我上面的註釋行,去掉遠端呼叫的註釋,對下面的本地呼叫註釋,編譯後啟動服務端,再啟動客戶端就是遠端呼叫了。



非同步呼叫結束,立即就能顯示出結果,如果開啟遠端方法的話,可以看的更加清晰:
客戶端:主執行緒方法開始-》服務端:非同步方法開始-》服務端:非同步方法結束-》客戶端:結果是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(
"非同步方法結束");
        }
    }        

[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(
"主執行緒方法結束");
        }
    }
}

這次我們僅僅只能在遠端除錯,我們先讓非同步方法去做,然後就放心的做主執行緒的事情,其他不管了。

執行結果我描述一下:
客戶端:主執行緒方法開始-》服務端:非同步方法開始-》服務端:非同步方法結束-》客戶端:主執行緒方法結束-》客戶端:用了3.8秒。

上面說的三種方法,只是非同步程式設計的一部分,具體怎麼非同步呼叫遠端方法要結合實際的例子,看是否需要用到方法的返回和主執行緒方法的執行時間與遠端方法執行時間等結合起來考慮,比如上述的WaitHandle也可以用輪詢來實現:
while(Iar.IsCompleted==false) System.Threading.Thread.Sleep(10);總的來說遠端物件的非同步操作和本地物件的非同步操作是非常接近。

還可以參考msdn相關文章:
http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cpguide/html/cpconasynchronousprogrammingdesignpattern2.asp

相關文章