我正在为我的EventLoop类编写一个异步成员函数,该成员函数会将繁重的计算任务投递到全局线程池中,然后获取该任务的结果并排队一个完成回调函数,该回调函数将前一个的结果用作返回给EventLoop的参数,以便稍后让事件循环处理(调用)完成回调。
因此,通常异步成员函数应将两个函数用作参数,第一个被设计为任何返回类型的任务函数,第二个是将第一个函数的返回值作为参数的finish回调。参数。
我的第一次尝试代码如下:
class EventLoop
{
public:
using DeferCallback=std::function<void()>;
//call by another thread to queue work into the eventloop
void queue_work(DeferCallback cb);
template<typename F,typename ...Args>
void async(F &&task_func,Args&& ...args,std::function<void(std::invoke_result_t<F,Args...>&)> &&finish_cb){
g_threadpool::get_instance()->post([this,task_func,args...,finish_cb](){
using task_ret_t=std::invoke_result_t<F,Args...>;
task_ret_t res=task_func(args...);
this->queue_work([finish_cb,&res](){
finish_cb(res);
});
});
}
//for task func of void return type
template<typename F,typename ...Args>
void async(F &&task_func,Args&& ...args,std::function<void(void)> &&finish_cb){
g_threadpool::get_instance()->post([this,task_func,args...,finish_cb](){
task_func(args...);
this->queue_work([finish_cb](){
finish_cb();
});
});
}
我进行了测试,发现它仅在不向... args传递任何内容或编译器无法正常工作时才起作用。然后,我进行搜索并找到以下问题:Parameter pack must be at the end of the parameter list... When and why? 它基本上告诉我:
[...]如果主类模板或别名> template的模板参数是模板参数包,则它应该是最后一个template-> parameter。[...]
如果必须显式实例化异步功能,将很难使用。
然后我尝试一下:
template<typename Ret,typename ...Args>
void async(std::function<Ret(Args...)> &&task_func,std::function<void(Ret&)> &&finish_cb){ ... }
,发现除了std :: function之外,我无法将lambda函数或成员函数传递给第一个参数,这并不理想。相关问题:Deduce template argument from std::function call signature
那么有什么办法可以解决?
答案 0 :(得分:2)
如果我必须显式实例化异步函数,将很难使用。
在您的情况下,这特别困难,因为给定签名
template <typename F, typename ... Args>
void async (F &&task_func,
Args && ...args,
std::function<void(std::invoke_result_t<F,Args...>&)> &&finish_cb)
(并且忽略了Args... args
应该位于最后位置的问题)F
和Args...
都以最后一个参数(finish_cb
的类型出现,因此,F
和Args...
的推论还取决于最后一个参数,而不是与前面的参数必须一致。
因此,例如,您不能将lambda传递给最后一个参数,因为lambda可以转换为std::function
但不是std::function
,因此F
和Args...
不能从lambda推论得出。
那么有什么办法可以解决?
建议:完全避免使用std::function
并将两个可调用的参数放在前面,后面的(可变)参数放在后面。
我的意思是...如下(但请注意:未经测试的代码和完善的转发都被忽略了)
template <typename F1, typename F2, typename ...Args>
void async (F1 && task_func, F2 && finish_cb, Args && ... args) {
g_threadpool::get_instance()->post([this,task_func,args...,finish_cb](){
auto res=task_func(args...);
this->queue_work([finish_cb,&res](){
finish_cb(res);
});
});
}
如果您的问题是您需要两个版本的async()
,一个版本的F1
返回void
(不返回值),另一个版本的F1
返回一个值,您可以将std::invoke_result_t
与SFINAE一起使用
// void (no res) version
template <typename F1, typename F2, typename ...Args>
std::enable_if_t<true == std::is_same_v<void, std::invoke_result_t<F,Args...>>>
async (F1 && task_func, F2 && finish_cb, Args && ... args)
{ /* do something */ }
// non-void (with res) version
template <typename F1, typename F2, typename ...Args>
std::enable_if_t<false == std::is_same_v<void, std::invoke_result_t<F,Args...>>>
async (F1 && task_func, F2 && finish_cb, Args && ... args)
{ /* do something */ }