深圳夜归人

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

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

注:里面使用了一个自己写的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  阅读(2049)  评论(5编辑  收藏  举报