1 简介
AMI
异步方法调用(AMI) 这个术语描述的是客户端的异步编程模型支持。如
果你使用AMI 发出远地调用,在Ice run time 等待答复的同时,发出调用的 线程不会阻塞。相反,发出调用的线程可以继续进行各种活动,当答复最 终到达时, Ice run time 会通知应用。通知是通过回调发给应用提供的编程 语言对象的AMD
一个服务器在同一时刻所能支持的同步请求数受到Ice run time 的服务
器线程池的尺寸限制(参见15.3 节)。如果所有线程都在忙于分派长时间 运行的操作,那么就没有线程可用于处理新的请求,客户就会经验到不可 接受的无响应状态。 异步方法分派(AMD) 是AMI 的服务器端等价物,能够解决这个可伸缩 性问题。在使用AMD 时,服务器可以接收一个请求,然后挂起其处理, 以尽快释放分派线程。当处理恢复、结果已得出时,服务器要使用Ice run time 提供的回调对象,显式地发送响应。 用实际的术语说, AMD 操作通常会把请求数据(也就是,回调对象和 操作参数)放入队列 ,供应用的某个线程(或线程池)随后处理用。这 样,服务器就使分派线程的使用率降到了最低限度,能够高效地支持数千 并发客户。 另外, AMD 还可用于需要在完成了客户的请求之后继续进行处理的操 作。为了使客户的延迟降到最低限度,操作在返回结果后,仍留在分派线 程中,继续用分派线程执行其他工作。2 使用元数据修改代码生成
程序员如果想要使用异步模型(AMI、AMD,或两者都使用),需要给
Slice 定义批注上元数据(4.17 节)。程序员可以在两个层面上指定这种元数 据:接口或类的层面,或单个操作的层面。如果你是在为一个接口或类进 行指定,那么为它的所有操作生成的代码都将会有异步支持。而如果只有 某些操作需要异步支持,那么你可以只为这些操作指定元数据,从而使生 成的代码的数据降到最低限度。考虑下面的Slice 定义:
["ami"] interface I { bool isValid(); float computeRate(); }; interface J { ["amd"] void startProcess(); ["ami", "amd"] int endProcess(); }; 在这个例子中,为接口I 的所有代理方法生成的代码都将具有同步和异 步调用支持。在接口J 中, startProcess 操作使用的是异步分派,而 endProcess 操作则支持异步的调用和分派。3 使用AMI的代码映射
3.1 C++ 代码生成器为每个AMI 操作生成以下代码:
(1)一个抽象的回调类, Ice run time 用它来通知应用,操作已完成。类名是按这样的模式取的:AMI_class_op。
这个类提供两个方法:
void ice_response(<params>);
void ice_exception(const Ice::Exception &);
(2)一个额外的代理方法,其名字是操作在映射后的名字,加上_async。
这个方法的返回类型是void,第一个参数是一个智能指针,指向上面 描述的回调类的一个实例。其他的参数由操作的各个in 参数组成,次 序是声明时的次序。例如,假定我们定义了下面这个操作:
interface I { ["ami"] int foo(short s, out long l); }; 下面是为操作foo 生成的回调类: class AMI_I_foo : public ... { public: virtual void ice_response(Ice::Int, Ice::Long) = 0; virtual void ice_exception(const Ice::Exception &) = 0; }; typedef IceUtil::Handle<AMI_I_foo> AMI_I_fooPtr; 下面是为操作foo 的异步调用生成的 代理方法: void foo_async(const AMI_I_fooPtr &, Ice::Short);3.2 一个使用AMI的例子
为了演示Ice 中的AMI 的用法,让我们定义一个简单的计算引擎的
Slice 接口 sequence<float> Row; sequence<Row> Grid; exception RangeError {}; interface Model { ["ami"] Grid interpolate(Grid data, float factor) throws RangeError; };C++ 客户
我们首先必须定义我们的回调实现类,它派生自生成的 AMI_Model_interpolate 类: class AMI_Model_interpolateI : public AMI_Model_interpolate { public: virtual void ice_response(const Grid & result) { cout << "received the grid" << endl; // ... postprocessing ... } virtual void ice_exception(const Ice::Exception & ex) { try { ex.ice_throw(); } catch (const RangeError & e) { cerr << "interpolate failed: range error" << endl; } catch (const Ice::LocalException & e) { cerr << "interpolate failed: " << e << endl; } } };调用interpolate 的代码同样直截了当:
ModelPrx model = ...; AMI_Model_interpolatePtr cb = new AMI_Model_interpolateI; Grid grid; initializeGrid(grid); model->interpolate_async(cb, grid, 0.5); 在获取了Model 对象的代理之后,客户实例化一个回调对象,初始化一 个栅格,然后调用interpolate 的异步版本。当Ice run time 接收到对这个 请求的响应时,会调用客户提供的回调对象。3.3 并发问题
在Ice 中,异步调用由客户线程池提供支持 ( 第15 章),池中的线程主 要负责处理答复消息。理解下列与异步调用相关的并发问题很重要: • 一个回调对象不能同时用于多个调用。需要聚合来自多个答复的信息的 应用可以创建一个单独的对象,让回调对象对它进行委托。 • 对回调对象的调用来自Ice run time 的客户线程池中的线程,因此,如果 在答复到达的同时,应用可能要与回调对象进行交互,就有可能需要进 行同步。 • 客户线程池中的线程的数目决定了,同时可以为多少异步调用发出回 调。客户线程池的缺省尺寸是一,意味着对回调对象的调用是序列化 的。如果线程池的尺寸增大了,而同一回调对象被用于多个调用,应用 就可能需要进行同步。注:本文内容主要来源于Ice官方文档