異步方法調用(Asynchronous Method Invocation,簡稱AMI)
下面這種情況就是AMI調用:“斧頭幫”大哥(客戶端)叫小弟(服務器端)去幹收租的活(遠程調用),並且給小弟一把煙花炮竹(回調類)。囑咐說: “我還有其它事情要打掃打掃,如果你的事情辦完了,就放'OK'煙花;如果遇到反抗,就放'斧頭'煙花!”(服務器答覆)。說完,這位大哥就可以放心的做其它事去了,直到看到天上煙花盛開,根據"OK"或"斧頭"狀再作處理。
AMI是針對客戶端而言的,當客戶端使用AMI發出遠程調用時,客戶端需要提供一個實現了回調接口的類用於接收通知。然後不等待調用完成立即返回,這時可以繼續其它活動,當得到服務器端的答覆時,客戶端的回調類中的方法就會被執行。
例:修改原Helloworld 客戶端,使用異步方法遠程調用printString。
首先,要修改原來的Printer.ice定義文件,在printString方法前加上["ami"]元標識符。
- module Demo{
- interface Printer
- {
- ["ami" ] void printString(string s);
- };
- };
同樣,再用slice2cpp Printer.ice生成Printer.h和Printer.cpp文件,並把這兩個文件加入原項目(如果是直接修改之前的代碼的話,因爲原先已經加入了這兩個文件,這步可以跳過)。
觀察生成的Printer.h文件,可以找到這個定義:
- namespace Demo
- {
- class AMI_Printer_printString : public ::IceInternal::OutgoingAsync
- {
- public :
- virtual void ice_response() = 0;
- virtual void ice_exception( const ::Ice::Exception&) = 0;
- ...
- };
- }
這裏的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方法:
- namespace IceProxy //是代理類
- {
- namespace Demo
- {
- class Printer : virtual public ::IceProxy::Ice::Object
- {
- ...
- public :
- bool printString_async( const ::Demo::AMI_Printer_printStringPtr&,
- const ::std::string&);
- bool printString_async( const ::Demo::AMI_Printer_printStringPtr&,
- const ::std::string&, const ::Ice::Context&);
- ...
- };
- }
- }
結合這兩樣東西(AMI_Printer_printString 接口和printString_async 方法),我們的客戶端AMI調用方法爲:
- 實現AMI_Printer_printString接口的ice_response和ice_exception方法,以響應遠程調用完成後的工作。
- 把上面實現的回調對象作爲printString_async的參數啓動遠程調用,然後可以做其它事了。
- 當得到服務端答覆後,AMI_Printer_printString接口的ice_response的方法被調用。
演示代碼(客戶端):
- #include <ice/ice.h>
- #include <printer.h>
- using namespace std;
- using namespace Demo;
- //實現AMI_Printer_printString接口
- struct APP : AMI_Printer_printString
- {
- virtual void ice_response()
- {
- cout << "printString完成" << endl;
- }
- virtual void ice_exception( const ::Ice::Exception& e)
- {
- cout << "出錯啦:" << e << endl;
- }
- };
- class MyApp: public Ice::Application{
- public :
- virtual int run( int argc, char *argv[])
- {
- Ice::CommunicatorPtr ic = communicator();
- Ice::ObjectPrx base =
- ic->stringToProxy("SimplePrinter:default -p 10000" );
- Demo::PrinterPrx printer = PrinterPrx::checkedCast(base);
- if (!printer) throw "Invalid Proxy!" ;
- // 使用AMI異步調用
- printer->printString_async(new APP, "Hello World!" );
- cout << "做點其它的事情..." << endl;
- system("pause" );
- return 0;
- }
- };
- int main( int argc, char * argv[])
- {
- MyApp app;
- return app.main(argc,argv);
- }
服務端代碼不變,編譯運行,效果應該是調用printer->printString_async之後還能"做點其它的事情...",當服務端完成後客戶端收到通知,顯示"printString完成"。
另外,爲了突出異步效果,可以修改服務器端代碼,故意把printString執行得慢一點:
- struct PrinterImp : Printer{
- virtual void printString( const ::std::string& s,
- const ::Ice::Current&)
- {
- Sleep(1000);
- cout << s << endl;
- }
- };
異步方法分派(Asynchronous Method Dispatch,簡稱AMD)
AMD是針對服務器端而言的,在同步的情況下,服務器端收到一個調用請求後,在線程池中拿出一個空閒線程用於執行這個調用。這樣,服務器在同一時刻所能支持的同步請求數受到線程池大小的限制。
如果線程池內的線程都在忙於執行長時間的操作,那麼新的請求到來時就會處於長時間得不到答覆的狀態,這可能會造成客戶端長時間等待(如果客戶端沒使用AMI的話)。
ICE的解決方法是:服務器收到請求時並不馬上執行具體工作,而是把執行這項工作所需的參數以及回調類保存到一個地方(比如隊列)後就返回。而另外的線程(或線程池)負責取出保存的參數並執行之,執行結束後使用回調類通知客戶端工作已完成(或異常)。
還是用上面“斧頭幫”來舉例:“斧頭幫”大哥(客戶端)叫小弟(服務器端)去幹收租的活(遠程調用),這位小弟並不是馬上就去收租去了,而是把這件工作記錄到他的日程表裏(同時還有好幾個老闆叫他幹活呢,可憐的人啊~~)。然後等有空的時候再按日程表一項項的做(或者叫其它有空的弟兄幫忙做),做完工作後該放煙花的就放煙花(回調智能客戶端),該砍人的就放信號彈啥的。
例:修改原Helloworld 服務器端,使用異步方法分派處理printString方法。
首先,要修改原來的Printer.ice定義文件,在printString方法前加上["amd"]元標識符。
- module Demo{
- interface Printer
- {
- ["amd" ] void printString(string s);
- };
- };
同樣,再用slice2cpp Printer.ice生成Printer.h和Printer.cpp文件,並把這兩個文件加入原項目(如果是直接修改之前的代碼的話,因爲原先已經加入了這兩個文件,這步可以跳過)。
觀察生成的Printer.h文件,可以發現和AMI類似的一個回調接口AMD_Printer_printString :
- namespace Demo
- {
- class AMD_Printer_printString : virtual public ::IceUtil::Shared
- {
- public :
- virtual void ice_response() = 0;
- virtual void ice_exception( const ::std::exception&) = 0;
- virtual void ice_exception() = 0;
- };
- ...
- }
這個回調接口由ICE自己實現,我們只要拿來用就可以了。在哪裏用呢?馬上就會發現:我們要實現的Printer接口的printString 方法不見了,取而代之的是printString_async 方法:
- namespace Demo
- {
- class Printer : virtual public ::Ice::Object
- {
- ...
- virtual void printString_async(
- const ::Demo::AMD_Printer_printStringPtr&,
- const ::std::string&, const ::Ice::Current& = ::Ice::Current()) = 0;
- ...
- };
- }
這個printString_async 方法就是我們要實現的異步分派方法,它的第一個參數就是由ICE實現的回調類AMD_Printer_printString ,在這個方法裏,我們要兩種方案:
- 直接做具體工作,完成後在末尾調用回調類的ice_response方法告知客戶端已完成。這種方案就和之前普通版的服務端一樣,是同步執行的。
- 把回調類和請求所需要的參數放入一個指定的位置,再由其它線程取出執行和通知客戶端。這種方案就是異步分派方法,具體實現時還可以有多種方式,如使用命令模式把參數和具體操作直接封裝成一個對象放入隊列,然後由另一線程(或線程池)取出執行。後面的示例代碼爲了簡單起見直接使用了Windows API中的線程池功能,而且也沒有使用隊列。
示例代碼
- #include <ice/ice.h>
- #include "printer.h"
- using namespace std;
- using namespace Demo;
- // 傳遞給線程函數的參數
- struct CallbackEntry{
- AMD_Printer_printStringPtr callback;
- string str;
- };
- // 線程函數
- DWORD WINAPI DoPrintString( LPVOID lpParameter)
- {
- // 取得參數
- CallbackEntry *pCE = (CallbackEntry *)lpParameter;
- // 工作:打印字符(延時1秒模擬長時間操作)
- Sleep(1000);
- cout << pCE->str << endl;
- // 回調,工作完成。如果工作異常,則調用ice_exception();
- pCE->callback->ice_response();
- // 刪除參數(這裏使用堆直接傳遞,其實更好的方法是使用隊列)
- delete pCE;
- return TRUE;
- }
- struct PrinterImp : Printer{
- virtual void printString_async(
- const AMD_Printer_printStringPtr &callback,
- const string& s, const Ice::Current&)
- {
- // 參數打包(回調類和pringString方法的參數)
- CallbackEntry *pCE = new CallbackEntry;
- pCE->callback = callback;
- pCE->str = s;
- // 讓Windows線程池來執行具體任務
- ::QueueUserWorkItem(DoPrintString,pCE,WT_EXECUTEDEFAULT);
- }
- };
- class MyApp : public Ice::Application{
- public :
- virtual int run( int n, char * v[]){
- Ice::CommunicatorPtr& ic = communicator();
- Ice::ObjectAdapterPtr adapter
- = ic->createObjectAdapterWithEndpoints("SP" , "default -p 10000" );
- Ice::ObjectPtr object = new PrinterImp;
- adapter->add(object, ic->stringToIdentity("SimplePrinter" ));
- adapter->activate();
- ic->waitForShutdown();
- return 0;
- }
- };
- int main( int argc, char * argv[])
- {
- MyApp app;
- return app.main(argc, argv);
- }
客戶端不需要改變,編譯運行服務器然後用客戶端測試效果。(其實效果不是很明顯,因爲AMD提高的是服務器的負荷能力)