注:里面使用了一个自己写的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 (F
{
_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 (F
{
_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条,还需要实现使用异步模型模拟同步调用机制;
以上只是现在的初步设想,可能有遗漏之处。
六、发点劳骚,提点建议。
我不明白为何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 (F
{
_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 (F
{
_call += f1;
_call_result += f2;
}
void init_arguments (ArgTypes args)
{
_params = args;
}
virtual void call ()
{
_call (_params);
_call_result (_params);
}
}
一个typelist就代替了这么多版本的偏特化,不知道这个想法是不是可行。