深圳夜归人

繁华的都市,有谁记得我们的脚步?

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  32 随笔 :: 0 文章 :: 64 评论 :: 1 引用

注:里面使用了一个自己写的C++委托类Delegate,不是.net中的委托,关于这个委托详见我的C++委托类最终版。本文所写的代码是跨平台的纯C++实现。

一、简要说明。

这里讲的异步调用代理组件,简要描述如下。

假如有一个耗时的方法(不论它是本地调用还是远程调用),这个调用耗时的原因是由于某些原因的阻塞,比如IO,通常可以把这个操作交给一个线程去处理,为了通用、高效,往往会实现一个领导者/追随者线程池。常见的处理方式是把每一个调用封装为一个CALL对象,把参数存储在这个对象中,然后调用它的处理方法,在这个处理方法中调用实际的处理函数,返回值将会存储在CALL对象的一个成员变量中。

从我使用过的框架来看,大部分是采用编写一个接口描述语言,并由一些工具自动生成相应的同步/异步调用过程。有的还要求你为每一个调用编写一个派生类,甚至需要另外一个类处理返回值或异常,这几个类完成一个同步->异步的映射,处理起来非常繁琐。

这些复杂性都是由于线程模型的复杂性引起的。

二、思路。

我构思了一个异步调用的基础组件,大致描述如下:

1
、每一个调用需要绑定一个实际调用的函数call和一个处理调用结果的函数call_result,它们的类型映身如下:

a.
如果call类型是void(...),则call_result类型也是void(...)
b.
如果call类型是return_type(...),即有返回值的,则call_result类型是void(return_type, ...)

假定这个类的名字是Call,那么它的使用方式是:
void call_1 (int);

void call_result_1 (int);

Call <void(int)> call_1 (call_1, call_result_1);

 

int call_2 (int);

void call_result_2 (int, int);

Call <void(int)> call_2 (call_2, call_result_2);

其中还要求能够接受绑定了this指针的成员函数(实际上就是对象指针、成员函数指针对)、仿函数,前面做委托类时已经可以做到这点了,所以暂时不考虑细节。

2
、现在假如要完成一个异步调用,整个过程就包括:a.生成一个异步调用对象并返回b.工作线程取出调用对象并执行,调用完成并回调通知。

以上面的call_1为例,这3个步骤如下:

a.
生成一个异步调用对象并返回:
// 生成调用并保存参数,放入处理队列

Call <void(int)>* pCall = new Call <void(int)>(call_1, call_result_1);

pCall->init_arguments (arguments);

asynch_call_queue.push(call_1);


b.
工作线程取出调用对象并执行:
CallBase* pCall = asynch_call_queue.front ();

asynch_call_queue.pop ();

pCall->call (); // 将执行调用并回调通知。

delete pCall; // 处理后事

本文的重点并不在线程队列上,而是放在如何生成这个这个映射上面。

三、实现。

从上面可以整理出一个最简单的部分:
struct CallBase

{

    virtual ~CallBase () {}

    virtual void call () = 0;

};

 

typedef queue <CallBase*> AsynchCallQueue;

 

只要从CallBase上派生模板类Call即可,由于需要编写多个偏特化版本,比较耗时,这里先实现一个R(T)void(T)特化版本:

 

template <typename T>

class Call

{

     Call ();

};

 

template <typename Ret, typename A>

class Call <Ret(A)> : public CallBase

{

     typename Delegate <Ret(A)> _call;

     typename Delegate <void(Ret,A)> _call_result;

     Ret      _response;

     A        _param1;

public:

     template <typename F1, typename F2>

     Call (F1 f1, F2 f2)

     {

         _call += f1;

         _call_result += f2;

     }

 

     void init_arguments (A a)

     {

         _param1 = a;

     }

 

     virtual void call ()

     {

         _response = _call (_param1);

         _call_result (_response, _param1);

     }

};

 

template <typename A>

class Call <void(A)> : public CallBase

{

     typename Delegate <void(A)> _call;

     typename Delegate <void(A)> _call_result;

     A        _param1;

public:

     template <typename F1, typename F2>

     Call (F1 f1, F2 f2)

     {

         _call += f1;

         _call_result += f2;

     }

 

     void init_arguments (A a)

     {

         _param1 = a;

     }

 

     virtual void call ()

     {

         _call (_param1);

         _call_result (_param1);

     }

};

 

四、简单测试效果:

int test1 (int n)

{

     cout << "test1: " << n << endl;

     return 3;

}

 

void test1_result (int ret, int n)

{

     cout << "test1_result: " << ret << ", " << n << endl;

}

 

void test2 (int n)

{

     cout << "test2" << n << endl;

}

 

void test2_result (int n)

{

     cout << "test2_result: " << n << endl;

}

 

AsynchCallQueue asynch_call_queue;

 

// 此处三行定义是由于VC2005 Beta 2的一个偏特化BUG,如果不定义,则会提示无法决议。

// g++编译器下可以直接去掉这几行。

Delegate <void(int,int)> d;

Delegate <void(int)> d1;

Delegate <int(int)> d2;

 

int main ()

{

     // 生成调用并放入队列

     {

         Call <int(int)>* pCall1 = new Call <int(int)> (test1, test1_result);

         pCall1->init_arguments (3);

         asynch_call_queue.push (pCall1);

 

         Call <void(int)>* pCall2 = new Call <void(int)> (test2, test2_result);

         pCall2->init_arguments (5);

         asynch_call_queue.push (pCall2);

     }

     // 模拟另一线程取出并执行

     {

         CallBase* pCall;

         while (!asynch_call_queue.empty ())

         {

              pCall = asynch_call_queue.front ();

              pCall->call ();

              asynch_call_queue.pop ();

              delete pCall;

         }

     }

     return 0;

}

 

五、补充。

这里说的“异步调用代理组件”,其实就是Call类,由于使用了委托类,所以绑定异步调用更灵活。

本文只是简单的一点思路,离实用的距离还有:

1、   未加入线程池,未实现同步队列;

2、   模板类参数类型未严格约束,可能导致某些编译错误很难找出错误原因;

3、   这个模型没有处理“生成调用和回调处于同一线程”这个要求,这个会增加一些复杂度,比如需要为每个call映射线程ID、要为每个call分配全局唯一ID,为了让调用过程由线程池完成,调用结果由生成调用的线程完成,需要把这2个部分分离

4、   需要加入调用超时设置;

5、   基于第3条和第4条,还需要实现使用异步模型模拟同步调用机制;

6、这里给出的代码只适用于传值参数(指针也适合),引用类型暂未考虑。


以上只是现在的初步设想,可能有遗漏之处。

 

六、发点劳骚,提点建议。

我不明白为何C++标准不把委托置于标准之内,c++0x里面好像也没看到,不过还需要几年时间,也可能会加入。

另外在写委托模板类和这里的Call类时,就觉得处理不同个数的参数实在繁琐,假如C++模板中加入下面这个东西多好啊。。。(我称它为模板宏,其实就是加入一个typelist关键字并运行这种机制,相应还有valuelist,不过想法还不成熟)

template <typename Ret, typelist ArgTypes>

class Call <Ret(ArgTypes)>

{

     typename Delegate <Ret(ArgTypes)> _call;

     typename Delegate <void(Ret,ArgTypes)> _call_result;

     ArgTypes _params;

     Ret      _response;

public:

     template <typename F1, typename F2>

     Call (F1 f1, F2 f2)

     {

         _call += f1;

         _call_result += f2;

     }

 

     void init_arguments (ArgTypes args)

     {

         _params = args;

     }

 

     virtual void call ()

     {

         _response = _call (_params);

         _call_result (_response, _params);

     }

}

 

template <typename Ret, typelist ArgTypes>

class Call <Ret(ArgTypes)>

{

     typename Delegate <void(ArgTypes)> _call;

     typename Delegate <void(ArgTypes)> _call_result;

     ArgTypes _params;

public:

     template <typename F1, typename F2>

     Call (F1 f1, F2 f2)

     {

         _call += f1;

         _call_result += f2;

     }

 

     void init_arguments (ArgTypes args)

     {

         _params = args;

     }

 

     virtual void call ()

     {

          _call (_params);

         _call_result (_params);

     }

}

一个typelist就代替了这么多版本的偏特化,不知道这个想法是不是可行。

posted on 2005-08-07 16:32 cpunion 阅读(813) 评论(5) 编辑 收藏

评论

#1楼 2005-08-07 18:06 Net66      
太复杂了,.net框架对异步调用有现在的实现.园内有文可参考:<br />1.&#160;<a class="singleposttitle" id="viewpost1_TitleUrl" href="http://www.cnblogs.com/net66/archive/2005/08/03/206132.html"><font color="#223355">衔接UI线程和管理后台工作线程的类(多线程、异步调用)</font></a>&#160; <br />2. <a class="singleposttitle" id="viewpost1_TitleUrl" href="http://www.cnblogs.com/net66/archive/2005/08/02/206067.html"><font color="#223355">通过多线程为基于 .NET 的应用程序实现响应迅速的用户</font></a>
 回复 引用 查看   

#2楼[楼主] 2005-08-07 19:18 cpunion      
那是使用.net,我这里委托是自己写的一个纯C++委托类,从调用来看并不复杂,实际上比.net那个还要简单,复杂在框架的建立,这是可重用的部分,只需写一次,复杂一点不算什么。

在这里发C++的东西真有点不合适,不过我找不到更好的地方,申请了好多都用不了。

我还是把标题改一下算了。
 回复 引用 查看   

#3楼[楼主] 2005-08-07 19:31 cpunion      
实际上打算完成一个C++的异步调用框架,下一步还想再完成一个分布式的,这需要把每个CALL序列化并发送到远端,并接收调用结果。这里有一点麻烦,是如何标识in和out参数,如果不采用IDL方案,就只好采用另一种复杂方案:所有参数都是in参数,所有非const引用或指针作为in/out参数,考虑到某些参数序列化后长度太大,可能还需要有一个附加的判断,就是in/out参数如果在调用结束后值有变化的话,才会传回,这可以解决没有IDL的问题。
 回复 引用 查看   

愚以为没有必要搞分布式的,这种构架在源码级别应用就可以了,二进制应用就没有必要了,毕竟有很多成熟的框架,更何况是分布式的,那等于在搞另一个rpc、另一个soap、另一个。。。,而且是simple的,那将失去实际的应用意义,毕竟有很多成熟的技术已经完成了远程procedure call,而且还不仅仅提供pc的功能。呵呵

此乃敝人之愚见,呵呵
 回复 引用   

#5楼[楼主] 2005-08-08 00:59 cpunion      
实际上做这个是想弥补我使用的SOAP工具包不能实现异步调用的缺点,现有的RPC平台不知道有没有保持连接的功能,没有具体测试。

我想能不能做成一个保持连接的,这样实际上可以替代现在的某些游戏系统使用的消息机制。ICE好像是可以,不过它的免费许可证要求公开源代码,否则只能使用商业许可,收费极高。。。

另外一个想自己实现的原因,是因为现在有的RPC平台层次太过模糊,完全无法在某些层次上插入一些处理,比如我想在发送一个远程调用以前使用我自己的加密算法,接收后使用自己的解密算法。

可能那些都是为了效率吧,不过我认为硬件、网络环境的发展将会可以忽略这点处理时间。
 回复 引用 查看