ICE的異步方法調用

異步方法調用(Asynchronous Method Invocation,簡稱AMI)

下面這種情況就是AMI調用:“斧頭幫”大哥(客戶端)叫小弟(服務器端)去幹收租的活(遠程調用),並且給小弟一把煙花炮竹(回調類)。囑咐說: “我還有其它事情要打掃打掃,如果你的事情辦完了,就放'OK'煙花;如果遇到反抗,就放'斧頭'煙花!”(服務器答覆)。說完,這位大哥就可以放心的做其它事去了,直到看到天上煙花盛開,根據"OK"或"斧頭"狀再作處理。

AMI是針對客戶端而言的,當客戶端使用AMI發出遠程調用時,客戶端需要提供一個實現了回調接口的類用於接收通知。然後不等待調用完成立即返回,這時可以繼續其它活動,當得到服務器端的答覆時,客戶端的回調類中的方法就會被執行。

例:修改原Helloworld 客戶端,使用異步方法遠程調用printString。

首先,要修改原來的Printer.ice定義文件,在printString方法前加上["ami"]元標識符。

  1. module Demo{
  2. interface Printer
  3. {
  4.     ["ami" void  printString(string s);
  5. };
  6. };

同樣,再用slice2cpp Printer.ice生成Printer.h和Printer.cpp文件,並把這兩個文件加入原項目(如果是直接修改之前的代碼的話,因爲原先已經加入了這兩個文件,這步可以跳過)。

觀察生成的Printer.h文件,可以找到這個定義:

  1. namespace  Demo
  2. {
  3. class  AMI_Printer_printString :  public  ::IceInternal::OutgoingAsync
  4. {
  5. public :
  6.     virtual   void  ice_response() = 0;
  7.     virtual   void  ice_exception( const  ::Ice::Exception&) = 0;
  8. ...
  9. };
  10. }

這裏的AMI_Printer_printString 就是printString方法的AMI回調接口,可以發現它AMI回調接口類名的規律是AMI_類名_方法名。

這個接口提供了兩個方法:

void ice_response(<params>);
    表明操作已成功完成。各個參數代表的是操作的返回值及out 參數。如果操作的有一個非 void返回類型,ice_response 方法的第一個參數就是操作的返回值。操作的所有out 參數都按照聲明時的次序,跟在返回值的後面。

void ice_exception(const Ice::Exception &);
    表明拋出了本地或用戶異常。

同時,slice2cpp還爲Printer代理類生成了異步版本的printString方法:

  1. namespace  IceProxy  //是代理類
  2. {
  3. namespace  Demo
  4. {
  5. class  Printer :  virtual   public  ::IceProxy::Ice::Object
  6. {
  7.     ...
  8. public :
  9.     bool  printString_async( const  ::Demo::AMI_Printer_printStringPtr&,
  10.         const  ::std::string&);
  11.     bool  printString_async( const  ::Demo::AMI_Printer_printStringPtr&,
  12.         const  ::std::string&,  const  ::Ice::Context&);
  13.     ...
  14. };
  15. }
  16. }

結合這兩樣東西(AMI_Printer_printString 接口和printString_async 方法),我們的客戶端AMI調用方法爲:

  1. 實現AMI_Printer_printString接口的ice_response和ice_exception方法,以響應遠程調用完成後的工作。
  2. 把上面實現的回調對象作爲printString_async的參數啓動遠程調用,然後可以做其它事了。
  3. 當得到服務端答覆後,AMI_Printer_printString接口的ice_response的方法被調用。

演示代碼(客戶端):

  1. #include <ice/ice.h>
  2. #include <printer.h>
  3.  
  4. using   namespace  std;
  5. using   namespace  Demo;
  6.  
  7. //實現AMI_Printer_printString接口
  8. struct  APP : AMI_Printer_printString
  9. {
  10.     virtual   void  ice_response()
  11.     {
  12.         cout << "printString完成"  << endl;
  13.     }
  14.     virtual   void  ice_exception( const  ::Ice::Exception& e)
  15.     {
  16.         cout << "出錯啦:"  << e << endl;
  17.     }
  18. };
  19.  
  20. class  MyApp:  public  Ice::Application{
  21. public :
  22.     virtual   int  run( int  argc,  char *argv[])
  23.     {
  24.         Ice::CommunicatorPtr ic = communicator();
  25.  
  26.         Ice::ObjectPrx base =
  27.             ic->stringToProxy("SimplePrinter:default -p 10000" );
  28.  
  29.         Demo::PrinterPrx printer = PrinterPrx::checkedCast(base);
  30.         if (!printer)  throw   "Invalid Proxy!" ;
  31.        
  32.         // 使用AMI異步調用
  33.         printer->printString_async(new  APP, "Hello World!" );
  34.         cout << "做點其它的事情..."  << endl;
  35.         system("pause" );
  36.         return  0;
  37.     }
  38. };
  39.  
  40. int  main( int  argc,  char * argv[])
  41. {
  42.     MyApp app;
  43.     return  app.main(argc,argv);
  44. }

服務端代碼不變,編譯運行,效果應該是調用printer->printString_async之後還能"做點其它的事情...",當服務端完成後客戶端收到通知,顯示"printString完成"。

另外,爲了突出異步效果,可以修改服務器端代碼,故意把printString執行得慢一點:

  1. struct  PrinterImp : Printer{
  2.     virtual   void  printString( const  ::std::string& s,
  3.         const  ::Ice::Current&)
  4.     {
  5.         Sleep(1000);
  6.         cout << s << endl;   
  7.     }
  8. };

異步方法分派(Asynchronous Method Dispatch,簡稱AMD)

AMD是針對服務器端而言的,在同步的情況下,服務器端收到一個調用請求後,在線程池中拿出一個空閒線程用於執行這個調用。這樣,服務器在同一時刻所能支持的同步請求數受到線程池大小的限制。

如果線程池內的線程都在忙於執行長時間的操作,那麼新的請求到來時就會處於長時間得不到答覆的狀態,這可能會造成客戶端長時間等待(如果客戶端沒使用AMI的話)。

ICE的解決方法是:服務器收到請求時並不馬上執行具體工作,而是把執行這項工作所需的參數以及回調類保存到一個地方(比如隊列)後就返回。而另外的線程(或線程池)負責取出保存的參數並執行之,執行結束後使用回調類通知客戶端工作已完成(或異常)。

還是用上面“斧頭幫”來舉例:“斧頭幫”大哥(客戶端)叫小弟(服務器端)去幹收租的活(遠程調用),這位小弟並不是馬上就去收租去了,而是把這件工作記錄到他的日程表裏(同時還有好幾個老闆叫他幹活呢,可憐的人啊~~)。然後等有空的時候再按日程表一項項的做(或者叫其它有空的弟兄幫忙做),做完工作後該放煙花的就放煙花(回調智能客戶端),該砍人的就放信號彈啥的。

例:修改原Helloworld 服務器端,使用異步方法分派處理printString方法。

首先,要修改原來的Printer.ice定義文件,在printString方法前加上["amd"]元標識符。

  1. module Demo{
  2. interface Printer
  3. {
  4.     ["amd" void  printString(string s);
  5. };
  6. };

同樣,再用slice2cpp Printer.ice生成Printer.h和Printer.cpp文件,並把這兩個文件加入原項目(如果是直接修改之前的代碼的話,因爲原先已經加入了這兩個文件,這步可以跳過)。

觀察生成的Printer.h文件,可以發現和AMI類似的一個回調接口AMD_Printer_printString

  1. namespace  Demo
  2. {
  3.  
  4. class  AMD_Printer_printString :  virtual   public  ::IceUtil::Shared
  5. {
  6. public :
  7.  
  8.     virtual   void  ice_response() = 0;
  9.     virtual   void  ice_exception( const  ::std::exception&) = 0;
  10.     virtual   void  ice_exception() = 0;
  11. };
  12. ...
  13. }

這個回調接口由ICE自己實現,我們只要拿來用就可以了。在哪裏用呢?馬上就會發現:我們要實現的Printer接口的printString 方法不見了,取而代之的是printString_async 方法:

  1. namespace  Demo
  2. {
  3. class  Printer :  virtual   public  ::Ice::Object
  4. {
  5.     ...
  6.     virtual   void  printString_async(
  7.         const  ::Demo::AMD_Printer_printStringPtr&,
  8.         const  ::std::string&,  const  ::Ice::Current& = ::Ice::Current()) = 0;
  9.     ...
  10. };
  11. }

這個printString_async 方法就是我們要實現的異步分派方法,它的第一個參數就是由ICE實現的回調類AMD_Printer_printString ,在這個方法裏,我們要兩種方案:

  1. 直接做具體工作,完成後在末尾調用回調類的ice_response方法告知客戶端已完成。這種方案就和之前普通版的服務端一樣,是同步執行的。
  2. 把回調類和請求所需要的參數放入一個指定的位置,再由其它線程取出執行和通知客戶端。這種方案就是異步分派方法,具體實現時還可以有多種方式,如使用命令模式把參數和具體操作直接封裝成一個對象放入隊列,然後由另一線程(或線程池)取出執行。後面的示例代碼爲了簡單起見直接使用了Windows API中的線程池功能,而且也沒有使用隊列。

示例代碼

  1. #include <ice/ice.h>
  2. #include "printer.h"
  3.  
  4. using   namespace  std;
  5. using   namespace  Demo;
  6.  
  7. // 傳遞給線程函數的參數
  8. struct  CallbackEntry{
  9.     AMD_Printer_printStringPtr callback;
  10.     string str;
  11. };
  12.  
  13. // 線程函數
  14. DWORD  WINAPI DoPrintString( LPVOID  lpParameter)
  15. {
  16.     // 取得參數
  17.     CallbackEntry *pCE = (CallbackEntry *)lpParameter;
  18.     // 工作:打印字符(延時1秒模擬長時間操作)
  19.     Sleep(1000);
  20.     cout << pCE->str << endl;   
  21.     // 回調,工作完成。如果工作異常,則調用ice_exception();
  22.     pCE->callback->ice_response();
  23.     // 刪除參數(這裏使用堆直接傳遞,其實更好的方法是使用隊列)
  24.     delete  pCE;
  25.     return  TRUE;
  26. }
  27.  
  28. struct  PrinterImp : Printer{
  29.     virtual   void  printString_async(
  30.         const  AMD_Printer_printStringPtr &callback,
  31.         const  string& s,  const  Ice::Current&)
  32.     {
  33.         // 參數打包(回調類和pringString方法的參數)
  34.         CallbackEntry *pCE = new  CallbackEntry;
  35.         pCE->callback = callback;
  36.         pCE->str = s;
  37.         // 讓Windows線程池來執行具體任務
  38.         ::QueueUserWorkItem(DoPrintString,pCE,WT_EXECUTEDEFAULT);
  39.     }
  40. };
  41.  
  42. class  MyApp :  public  Ice::Application{
  43. public :
  44.     virtual   int  run( int  n,  char * v[]){
  45.         Ice::CommunicatorPtr& ic = communicator();
  46.         Ice::ObjectAdapterPtr adapter
  47.             = ic->createObjectAdapterWithEndpoints("SP" , "default -p 10000" );
  48.         Ice::ObjectPtr object = new  PrinterImp;
  49.         adapter->add(object, ic->stringToIdentity("SimplePrinter" ));
  50.  
  51.         adapter->activate();
  52.         ic->waitForShutdown();
  53.         return  0;
  54.     }
  55. };
  56.  
  57. int  main( int  argc,  char * argv[])
  58. {
  59.     MyApp app;
  60.     return  app.main(argc, argv);
  61. }

客戶端不需要改變,編譯運行服務器然後用客戶端測試效果。(其實效果不是很明顯,因爲AMD提高的是服務器的負荷能力)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章