ICE中的异步程序设计方法(AMD,AMI)

1.AMI:AsynchronousMethod Invocation

异步方法调用

2.AMD:AsynchronousMethod Dispatch

异步方法派发

1.1同步模型与异步模型区别

两者的区别:

同步模型中发出调用的线程会阻塞到操作返回。异步模型则正好相反,发出调用的线程不会阻塞的等待调用结果的返回。

Ice 在本质上是一个异步的中间件平台,出于对应用(及其程序员)的考虑而模拟了同步的行为。当 Ice 应用通过代理、向远地对象发出同步的双向调用时,发出调用的线程会阻塞起来,以模拟同步的方法调用。在此期间,Ice run time在后台运行,处理消息,直到收到所需的答复为止,然后发出调用的线程就可以解除阻塞并解编结果。

1.2AMI概述

AMI:

异步方法调用这个术语描述的是客户端的异步编程模型支持。

使用 AMI发出远地调用,在Ice run time 等待答复的同时,发出调用的线程不会阻塞。相反,发出调用的线程可以继续进行各种活动,当答复最终到达时,Ice run time会通知应用。通过回调发给应用对象通知

1.3AMD概述

AMD:

使用 AMD 时,服务器可以接收一个请求,然后挂起其处理,以尽快释放分派线程。当处理恢复、结果已得出时,服务器要使用Ice run time 提供的回调对象,显式地发送响应。

AMD 操作通常会把请求数据 (也就是,回调对象和

操作参数)放入队列,供应用的某个线程 (或线程池)随后处理用。这样,服务器就使分派线程的使用率降到了最低限度,能够高效地支持数千并发客户。

为什么要使用AMD:

(1)默认情况下:一个服务器在同一时刻所能支持的同步请求数受到Ice run time的服务器线程池的尺寸限制。如果所有线程都在忙于分派长时间运行的操作,那么就没有线程可用于处理新的请求,客户就会体验到不可接受的无响应状态。

(2)Icerun time的服务器线程池尺寸受限于主机的CPU数

1.4元数据(metadata)与AMI,AMD

1)ICE自3.4以后,提供了一套全新的AMI(异步调用方式)的API,新的API已不需要在slice文件中标记[“ami”]这样的元数据。

2)ICE提供向下兼容性,支持[“ami”]的语言标记,但已标注为deprecated.

3)服务端使用AMD异步派发方式编程,仍然必须在slice文件中,对slice定义标注[“amd”]元数据。

Tips:

给客户提供某个调用方法的同步和异步版本是有益的,但如果在服

务器中这样做,程序员就必须实现两种版本的分派方法,并不能带来切实的好处,而且还会带来若干潜在的缺陷。

AMD元数据标记方式:

1)接口级(interface)

["amd"]interface I {

bool isValid();

float computeRate();

};

2)操作级(operation)

interface J {

["amd"] void startProcess();

int endProcess();

};

AMD元数据的标记方式

Tips:

在操作层面、而不是接口或类的层面指定元数据,不仅能使所生成的代码的数量降到最低限度,而且,更重要的是,它还能使复杂度降到最低限度。尽管异步模型更为灵活,它的使用也更复杂。因此,最好只把它用于那些能从中获得某种好处的操作;对其他操作则使用更简单的同步模型

2.1基本API

1.异步方法通知(AMI)API

ICE从3.4开始采用新的AMIAPI:

module Demo {

interface Employees {

string getName(intnumber);

};

};

1)不必再显式的注明[“ami”]元数据,slice工具会自动的生产同步调用与异步调用的stub代码。

Ice::AsyncResultPtr begin_getName(Ice::Int number);

Ice::AsyncResultPtr begin_getName(Ice::Int number,

const Ice::Context& __ctx);

std::string end_getName(const Ice::AsyncResultPtr&);

(2)begin_xxxMethod负责向队列中插入一个调用的请求,当请求插入到队列后,便可立即返回,不必阻塞的等待调用完成。

(3)end_xxxMethod负责收集异步调用的结果。当异步调用的结果还未处理完成时,调用end_xxxMethod方法会阻塞到异步调用完成处理结果。换句话说,如果结果已完成,调用end_xxxMethod方法会立即返回。

示例:

EmployeesPrxe = ...;

Ice::AsyncResultPtrr = e->begin_getName(99);

//Continue to do other things here...

stringname = e->end_getName(r);

AsyncResultPtr是AsynResult的智能指针,存储了ICE运行时对于异步调用的状态追踪的信息。在调用begin_xxxMethod方法相关的end_xxxMethod方法时,必须传入相应的AsyncResultPtr对象

(4)异步方法调用中的参数

Slice定义的方法参数在AMI的begin_,end_方法中有相应的变化:

doubleop(intinp1, string inp2, out booloutp1, out long outp2);

AMI:

Ice::AsyncResultPtrbegin_op(Ice::Intinp1,

const::std::string&inp2)

Ice::Doubleend_op(bool&outp1, Ice::Long& outp2,

constIce::AsyncResultPtr&);

注意:

1)slice定义中的输入参数转移为begin_op中的唯一输入参数。

2)slice定义中的输出参数作为end_op的输出参数,end_op的返回值为原slice定义的返回值。

  1. Begin_op返回AsyncResultPtr作为end_op的输入参数。

(5)异常处理(ExceptionHandling)

1)通常情况下,当异步调用过程中发生异常,在end_xxxMethod中抛出,即使异常发生在begin_xxxMethod中。

2)但有2种例外的情况:

ß当销毁了当前的Communicator后,再调用异步方法,会抛出CommunicatorDestroyedException异常。

ß当调用方式出错时,抛出IceUtil::IllegalArgumentException异常(begin_,end_配对错误,调用的方法参数错误)

2.2AsynResult类

AsyncResult类由begin_op方法返回,封装了异步调用的有关信息

classAsyncResult 
    : virtual public IceUtil::Shared, 
      private IceUtil::noncopyable{ 
public: 
    virtual booloperator==(const AsyncResult&)const; 
    virtual booloperator<(const AsyncResult&)const; 
 
    virtual IntgetHash() const; 
    virtual CommunicatorPtrgetCommunicator()const; 
    virtual ConnectionPtrgetConnection()const; 
    virtual ObjectPrxgetProxy() const; 
    conststring& getOperation()const; 
    LocalObjectPtrgetCookie() const; 
 
    boolisCompleted() const; 
    void waitForCompleted(); 
 
    boolisSent() const; 
    void waitForSent();  
    boolsentSynchronously()const; 
};

(2)方法说明

booloperator==(const AsyncResult&)const

booloperator<(const AsyncResult&)const

IntgetHash() const

比较函数与取hash值,用于AsynResult对象之间的比较,排序。特别是在外部有大量异步调用请求,在begin_op与end_op之间需要传递AsynResult对象时需要存储AysnResult对象时,这些方法可以将AsynResult对象作为主键存储在类似map这样的关联容器中,存储每个异步调用的状态。

CommunicatorPtrgetCommunicator()const

获取发送当前异步调用的通信器对象。

virtual ConnectionPtrgetConnection()const

获取当前调用使用的连接对象

virtualObjectPrx getProxy()const

获取发送异步调用的代理对象

conststring& getOperation()const

获取当前异步调用方法的名称

LocalObjectPtrgetCookie() const

获取begin_op发送的Cookie对象(后续介绍),如果begin_op没有发送Cookie,则返回Null

boolisCompleted() const

判断当前异步调用是否完成,当异步调用的结果数据的状态为有效时,返回true;反之,若结果数据状态为无效时,返回false

voidwaitForCompleted()

阻塞调用者,直达异步调用的结果数据状态为有效。

boolisSent() const

当begin_op操作被调用时,Ice运行时会将请求从客户侧的发送器发出,如果发送被客户侧的发送器拒绝,则向客户端侧的传输队列插入相关的调用请求。当这个请求成功发送时,返回true,当请求还在队列中时,返回false

voidwaitForSent()

阻塞等待客户侧将调用请求发出

boolsentSynchronously()const

当调用请求在第一次发送的时候,如果没有入队直接发出,返回true;反之返回false

2.3轮询方式的完成通知(Pollingfor Completion)

1)同步方式与异步方式效率上的差距

2)异步方式AsyncResult队列控制

提高传输效率

控制内存占用

2.通用回调函数方式的完成通知

相对于轮询方式的优点:

解除应用逻辑与异步调用机制的耦合性。利用Ice 运行时完成客户端的异步处理逻辑。

Slice提供的重载方法,红色高亮部分是回调完成通知的特征。

Ice::AsyncResultPtrbegin_getName(

Ice::Intnumber,

constIce::CallbackPtr& __del,

const Ice::LocalObjectPtr&__cookie = 0);

Ice::AsyncResultPtrbegin_getName(

Ice::Intnumber,

const Ice::Context&__ctx,

const Ice::CallbackPtr& __del,

const Ice::LocalObjectPtr&__cookie = 0);

Ice::CallbackPtr:Ice运行时提供的回调函数智能指针,存储了自定义的回调函数实例。

当发生回调时,Ice运行时会执行回调函数对象上绑定的方法。

3.自定义的回调对象示例:

classMyCallback :public IceUtil::Shared{

public:

void finished(constIce::AsyncResultPtr&r) {

EmployeesPrxe =

EmployeesPrx::uncheckedCast(r->getProxy());

try {

string name = e->end_getName(r);

cout<< "Name is: " << name << endl;

} catch (constIce::Exception& ex) {

cerr<< "Exception is: " << ex << endl;

}

}

};

typedefIceUtil::Handle<MyCallback>MyCallbackPtr;

回调对象封装了异步调用实质的业务逻辑,异步调用本身是发出非阻塞的调用请求,但重要的是发出了请求以后,应用逻辑该如何执行。

4.回调对象的编写要点

1)继承IceUtil::Shared

2)回调对象中的回调方法,必须符合以下声明方式:

void callback_method(AsyncResultPtr& r)

3)回调对象中的回调方法名称可以为合乎语法的任意名称。

4)回调方法中必须调用end_xxxMethod();

  1. 回调方法必须捕获所有可能由end_xxxMethod抛出的异常,如果遗漏了某异常,Ice运行时默认会在日志中记录,但也会忽略这个异常(设置Ice.Warn.AMICallback属性为0,可以不记录日志)。

5.应用程序AMI调用示例:

EmployeesPrxe = ...;

MyCallbackPtrcb =new MyCallback;

Ice::CallbackPtrd = Ice::newCallback(cb,

&MyCallback::finished);

e->begin_getName(99,d);

注意:

1)Ice::newCallback辅助函数,将自定义的回调对象智能指针与回调方法绑定后,返回Ice::CallbackPtr类型的对象。

  1. begin_xxxMethod,需要输入一个Ice::CallbackPtr对象。

6.回调中的Cookie

Ice::AsyncResultPtrbegin_getName(

Ice::Intnumber,

const Ice::CallbackPtr&__del,

const Ice::LocalObjectPtr& __cookie = 0);

Ice::AsyncResultPtrbegin_getName(

Ice::Intnumber,

const Ice::Context&__ctx,

const Ice::CallbackPtr&__del,

const Ice::LocalObjectPtr& __cookie = 0);

Cookie对象在异步方法调用中用于begin_xxxMethod与end_xxxMethod之间传递信息。

应用场景:

用户在界面上点击产生大量的异步调用,当这些异步调用完成后,每个调用需要更新不同用户界面组件。在这种情景下,每个调用需要携带它所需要更新的组件信息才能完成操作。

在这种异步调用与回调函数之间需要交互的情况下,可以通过Cookie在应用中绑定相关信息。

7.Cookie的编写

为了满足不同的应用场景,Cookie对象可以自定义。

Cookie编写的唯一要求就是要继承Ice::LocalObject

示例:

classCookie : public Ice::LocalObject

{

public:

Cookie(WidgetHandleh) : _h(h) {}

WidgetHandlegetWidget() {return _h; }

private:

WidgetHandle_h;

};

typedefIceUtil::Handle<Cookie>CookiePtr;

//

CookiePtrcookie1 = new Cookie(widgetHandle1);

//Make cookie for call to getName(42);

CookiePtrcookie2 = new Cookie(widgetHandle2);

//Invoke the getNameoperation with different cookies.

e->begin_getName(99,getNameCB,cookie1);

e->begin_getName(24,getNameCB,cookie2);

void

MyCallback::getName(constIce::AsyncResultPtr&r)

{

EmployeesPrxe =

EmployeesPrx::uncheckedCast(r->getProxy());

CookiePtrcookie =

CookiePtr::dynamicCast(r->getCookie());

try {

string name = e->end_getName(r);

cookie->getWidget()->writeString(name);

} catch (constIce::Exception& ex) {

handleException(ex);

}

}

1)在应用逻辑中创建了Cookie对象

2)将Cookie对象传递给begin_getName方法

3)在对应的回调方法中重新获得Cookie(需要向下转型)

  1. 从Cookie对象中获得需要的信息

类型安全回调函数方式的完成通知

(1)通用回调方法存在一些类型安全方面的隐患

ß在回调方法中调用end_xxxMethod前必须将代理对象向下转型为正确的代理对象类型

EmployeesPrx e = EmployeesPrx::uncheckedCast(r->getProxy());

ß调用的end_xxxMethod必须与begin_xxxMethod配对

ß如果使用了Cookie,那么也需要将Cookie向下转型为正确的Cookie类型。

CookiePtr cookie = CookiePtr::dynamicCast(r->getCookie());

ß在调用end_xxxMethod方法时,必须要捕获所有可能的异常。否则无法获知调用在何时失败

Slice工具提供了一套Callback类API解决上述类型安全问题。

类型安全回调函数方式的完成通知

(2)Slice工具与类型安全的回调

Slice工具在编译slice文件时,生成一系列相关的回调函数类别,在这些自动生成的代码中,将类型转换,调用end_xxxMethod等通用回调方法中的程式化方法以自动生成的方式代替手工编程。(C++中是以模板的方式实现不同回调函数的模板实例)

interfaceFileTransfer

{

void send(intoffset, ByteSeqbytes);

};

//同步发送

FileHandlefile = open(...);
FileTransferPrxft = ...;
constint chunkSize= ...;

Ice::Intoffset = 0;
while(!file.eof()) {
    ByteSeqbs;
    // Read a chunk
    bs= file.read(chunkSize);
    // Send the chunk
    ft->send(offset, bs);
    offset += bs.size();
}
FileHandlefile = open(...);
FileTransferPrxft =...; 
const intchunkSize= ...; 
Ice::Intoffset = 0; 
list<Ice::AsyncResultPtr>results; 
const intnumRequests= 5; 

while (!file.eof()){ 
   ByteSeqbs; 
   bs = file.read(chunkSize); 
   // Send up to numRequests+ 1 chunks asynchronously.
   Ice::AsyncResultPtrr = ft->begin_send(offset,bs); 
   offset += bs.size(); 
 
   // 等待请求完成发送
   r->waitForSent(); 
   results.push_back(r); 
   //超过了水位标则阻塞等待. 
   while (results.size()> numRequests){ 
        Ice::AsyncResultPtrr = results.front(); 
        results.pop_front(); 
        r->waitForCompleted(); 
   } 
}  

//等待剩余的请求完成.

while (!results.empty()){ 
   Ice::AsyncResultPtrr = results.front(); 
   results.pop_front(); 
   r->waitForCompleted(); 
}

2.4通用的完成通知回调函数(GenericCompletion Callbacks)

2.5类型安全的回调(Type-SafeCompletion Callbacks)

2.6单路调用(OnewayInvocations)

1)通用的回调方式由于回调类别中回调函数具有输入参数,

void callback_method(AsyncResultPtr& r)

因此是双路调用,无法调用单路的代理对象方法。

2)类型安全的回调方式,通过newCallback辅助方法,在回调函数对象中只指定失败的回调函数,可以调用单路方法。

示例:

MyCallbackPtrcb =new MyCallback;

Ice::Callback_Object_ice_pingPtrcallback =
   Ice::newCallback_Object_ice_ping(cb,&MyCallback::failureCB);

p->begin_opVoid(callback);

2.7流量控制(FlowControl)

1.流量控制的起因:

客户端并发请求非常频繁,服务端处理能力赶不上客户端发送异步请求的速度。客户端将请求发往客户端Ice运行时请求队列,队列不断增长,在不加流量控制的情况下,最终耗尽内存。

2.流量控制的功能:

Ice提供了一套流量控制管理的API,应用了流量管理的api后,当客户端的请求数超过了限定的阈值,则阻塞客户端发起的新的操作请求直到队列中完成部分请求的处理,有剩余的空间容纳入队的请求。

1)通用回调方式

voidsent(constIce::AsyncResult&);

调用情景1和情景2的判断由程序员调用AsyncResult::sentSynchronously判断,自行编写逻辑。

2)类型安全的回调方式

voidsent(bool sentSynchronously);

voidsent(bool sentSynchronously,const <CookiePtr>&cookie);

调用情景1与情景2的判断由Ice运行时传递的sendSynchronously参数判断。

3.流量控制的编程要点:

流量控制的基本思想是自定义流量控制的方法。

classMyCallback :public IceUtil::Shared{

public:

void finished(const Ice::AsyncResultPtr&);

voidsent(const Ice::AsyncResultPtr&); // 发送请求的方法,名字可以自定义

};

typedefIceUtil::Handle<MyCallback>MyCallbackPtr;

ß情景1:当Ice运行时将请求发送到客户端的发送端上后,由调用begin_xxxMethod的方法的线程接着调用send方法。

ß情景2:当Ice运行时将请求入队时,则由另一线程在Ice运行时将队列中请求发送到发送端后,调用send方法。

ß以上2种情况调用send的const Ice::AsyncResultPtr& 参数的sentSynchronously方法判断。

ß在send方法中对请求数进行计数,当队列的长度达到高水位标,则阻塞,当运行时将请求发往发送端,则减少计数。

2.8批量请求(BatchRequests)

1.批量调用的起因:

对于大量的零散单路调用或数据包请求,服务端在处理时需要频繁的在用户态和内核态之间转换,代价非常高昂。因此引入批量调用。

批量调用的方法:

Ice运行时提供了将客户端对象代理转换为批量调用代理的方法:

//C++

namespaceIceProxy {
namespaceIce {

classObject : /* ... */ {
public:
    //将普通代理转换为批量调用的代理
    Ice::ObjectPrx ice_batchOneway() const;
    Ice::ObjectPrx ice_batchDatagram() const;
    void ice_flushBatchRequests();
//...
};

}
}

显式调用与隐式调用:

显式同步调用:

ice_flushBatchRequests

显式异步调用:

begin_ice_flushBatchRequests

end_ice_flushBatchRequests

隐式调用:

Ice.BatchAutoFlush=1(默认)

Ice.MessageSizeMax=1MB(默认)

2.9并发(Concurrency)

ßIce运行时通常情况下由独立的线程调用异步回调方法中的回调函数。因此在回调函数中可以使用非递归锁而不怕出现死锁的情况。

ß

ß但如果定义了流量控制回调函数,则如前所述的规则,在不同的情景下,有不同的线程选择规则。

2.10限制(Limitations)

ß采用了AMI异步回调,则不能采用collocatedoptimization优化。否则抛出CollocationOptimizationException。

ßcollocatedoptimization优化参见ICE手册32.21节

2.11示例

#ifndefHELLO_ICE
#defineHELLO_ICE

moduleDemo
{

exceptionRequestCanceledException
{
};

interfaceHello
{
  // 注意元数据定义
    ["ami", "amd"]idempotent void sayHello(int delay)
        throws RequestCanceledException;

    void shutdown();
};

};

#endif

int
AsyncClient::run(intargc,char* argv[])
{
    if(argc> 1)
    {
        cerr<< appName()<< ": too many arguments" << endl;
        return EXIT_FAILURE;
    }

    HelloPrxhello = HelloPrx::checkedCast(communicator()->propertyToProxy("Hello.Proxy"));
    if(!hello)
    {
        cerr<< argv[0]<< ": invalid proxy" << endl;
        return EXIT_FAILURE;

    }
    menu();
  CallbackPtrcb =new Callback();
    char c;

do
   {
        try
        {
            cout<< "==> ";
            cin>> c;
            if(c == 'i')
            {
                hello->sayHello(0);
            }
            else if(c == 'd')
            {
                hello->begin_sayHello(5000,newCallback_Hello_sayHello(cb,&Callback::response, &Callback::exception));
            }
            else if(c == 's')
            {
                hello->shutdown();
            }
else
            {
                cout<< "unknown command `" << c << "'"
<< endl;
                menu();
            }
        }
        catch(constIce::Exception& ex)
        {
            cerr<< ex << endl;
        }
   }
   while(cin.good()&& c != 'x');

   return EXIT_SUCCESS;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值