这个问题有点杂,因为在程序中有时候需要单开一个新线程来执行一些操作,比如防止ui线程假死之类的,所以需要使用到多线程的东西,前面有过一个文章,就是写多线程的,用的是QThread来实现,需要继承,然后重写run函数,这样对于多次需要的程序就不太哦合适了,QRunnable也是需要继承派生。另外方式的话还有可以通过建立一个qobject然后将它move到其他的线程,这种方式也不是很方便,所以最后剩下QtConcurrent这个类,这个类实现的是通过传入的函数调用人,函数,还有参数,然后实现的到线程池里面获取当前可用的线程来执行这个传入的函数。
这里我干好就像看一下qt的信号槽,还有这个QtConcurrent,他们实现对于传入函数的可变类型,可变参数是怎么进行判断和最后调用执行的。刚好看到了豆子童鞋翻译的两个国外的文章http://www.devbean.net/2012/12/how-qt-signals-and-slots-work/,http://www.devbean.net/2012/12/how-qt-signals-and-slots-work-qt5/机制还是有一点复杂。qt的信号槽能够在运行的时候获取对象的信息,比如参数类型等,利用的是一个称为moc元对象编译。他会额外的生成一些多余的c++文件来记录运行时的信息,在这个文件中会有一个类似于索引表的东西记录了包括信号,参数,槽等等的索引信息。而对于传入可变类型,可变参数的函数,在c++中,如果不同类型的函数,例如成员非成员,他们的存储,便宜这些都会依赖于指向的类,所以强制转化统一处理就会出问题了。qt这里有一个QtPrivate::FunctionPointer类是来记录传入的指针的对象的一系列的信息,包括参数,类型,所在类(有的话)等等,还包括调用的信息。而对于处理,其实没有什么更方便的方法,只能通过模板来实现,对于不用的函数类型(普通函数指针,成员函数指针,const成员函数指针和仿函数(functors)等)都需要指定模板函数,然后对于不支持可变参数的c++版本,在每个函数类型上还要考虑不同的参数数目的模板。
比如假设是QtConcurrent::run函数,那么看他实现就会有个类型的模板:
template
QFuture run(T (*functionPointer)())
{
return (new StoredFunctorCall0(functionPointer))->start();
}
template
QFuture run(T (*functionPointer)(Param1), const Arg1 &arg1)
{
return (new StoredFunctorCall1(functionPointer, arg1))->start();
}
template
QFuture run(T (*functionPointer)(Param1, Param2), const Arg1 &arg1, const Arg2 &arg2)
。。。。。。
template
auto run(Functor functor) -> typename QtPrivate::QEnableIf::Value, QFuture >::Type
{
typedef decltype(functor()) result_type;
return (new StoredFunctorCall0(functor))->start();
}
template
auto run(Functor functor, const Arg1 &arg1)
-> typename QtPrivate::QEnableIf::Value, QFuture >::Type
{
typedef decltype(functor(arg1)) result_type;
return (new StoredFunctorCall1(functor, arg1))->start();
}
template
auto run(Functor functor, const Arg1 &arg1, const Arg2 &arg2)
-> typename QtPrivate::QEnableIf::Value, QFuture >::Type
{
typedef decltype(functor(arg1, arg2)) result_type;
return (new StoredFunctorCall2(functor, arg1, arg2))->start();
}.......
template
QFuture run(FunctionObject functionObject)
{
return (new StoredFunctorCall0(functionObject))->start();
}
template
QFuture run(FunctionObject functionObject, const Arg1 &arg1)
{
return (new StoredFunctorCall1(functionObject, arg1))->start();
}
template
QFuture run(FunctionObject functionObject, const Arg1 &arg1, const Arg2 &arg2)
{
return (new StoredFunctorCall2(functionObject, arg1, arg2))->start();
}.......
就大概一点点。太多了,信号槽的机制底层实现也基本就是如此的。
好了,然后我们既然要实现可变的参数,那还不如就直接使用QtConcurrent的实现,有得用当然是优先哈,然后我们就可以实现一个通用的类实现static函数来满足程序调用新线程任务时只需要一句话传处理函数,回调函数,参数这样的需求。而QtConcurrent::run函数会返回的qfuture对象可以给我们用来作为在线程处理完毕之后,启动回调函数的一个信号。然后就试一下代码了。
//分别对应于非成员函数和成员函数的模板(暂时先只是这两个类型,其它的需要可以继续添加上)
//然后为了函数比较的简洁,我们让参数只有一个,可以是基本类型,也可以是自定义的很多参数放置在一个结构体当中。
template
struct count_arg;
template
struct count_arg
{
typedef R result_type;
};
template
struct count_arg
{
typedef R result_type;
};
然后大概就写一下基本的代码,举了一个例子而已,全部比较多,但是原理就是这样了,所以应该都能看懂之后稍稍修改补全剩下的:
//成员函数连接到成员函数的方式,startTask传入的paraPointer都会在这里被delete,调用的时候不用再考虑delete传入的结构的问题,
//但是如果需要delete paraPointer里面的资源,则需要手动记录进行delete或者设置智能指针。
template
static void startTask(QObject *caller,Func1 taskFunc,QObject *cbCaller,Func2 cbFunc,structType paraPointer)
{
//使用模板来记录传入的函数的返回值,使用QFuture的时候会有需要用到。
typedef count_arg::result_type Func1RetType;
typedef count_arg::result_type Func2RetType;
qDebug()<<"member2member";
//用于监控线程函数执行完毕
QFutureWatcher* future_i = new QFutureWatcher();
//成员函数的转化成std::function相当于第一个参数就是对象的指针
std::function func = static_cast(cbFunc);
//线程函数调用结束后通过信函槽执行回调
connect(future_i,&QFutureWatcher::finished,cbCaller,[=](){
qDebug()<<"member2member concurrent return";
func(cbCaller,paraPointer);
//删除传入的结构
delete paraPointer;
future_i->deleteLater();
});
future_i->setFuture( QtConcurrent::run(caller,static_cast(taskFunc),paraPointer));
}
//成员函数连接到正常函数
template
static void startTask(QObject *caller,Func1 taskFunc,Func2 functor,structType paraPointer)
{
startTask(caller,taskFunc,functor,caller,paraPointer);
qDebug()<<"member2function: call member2functionAtContent";
}
//成员函数连接到需要在其他空间运行的函数
template
static void startTask(QObject *caller,Func1 taskFunc,Func2 functor,QObject *context,structType paraPointer)
{
typedef count_arg::result_type Func1RetType;
typedef count_arg::result_type Func2RetType;
qDebug()<<"member2functionAtContent";
QFutureWatcher* future_i = new QFutureWatcher();
std::function func = static_cast(functor);
connect(future_i,&QFutureWatcher::finished,context,[=](){
qDebug()<<"member2functionAtContent concurrent return";
func(paraPointer);
delete paraPointer;
future_i->deleteLater();
});
future_i->setFuture( QtConcurrent::run(caller,static_cast(taskFunc),paraPointer));
}
还有非成员到其他的等等的组合。。就不写了。。
然后调用就会变得比较简单,形式就和connect类似,频繁的使用就不会很烦了。
//新建一个线程运算
myStruct* paraStruct = new myStruct();//自定义结构包含需要的参数,当然如果是单一的基本类型的,就不需要结构了
xxx::startTask(this,&test::dataAnalysis,this,&test::displayAnalysis,paraStruct);
//这里test就是举例当前的类,dataAnalysis和displayAnalysis就分别代表处理函数以及回调函数
然后是两个函数:
void test::dataAnalysis(myStruct *parStruct)
{
...//这里可以做计算等等的你需要做的操作,并将需要在回调函数里获得的数据放在parStruct中或者修改。
}
void test::displayAnalysis(myStruct* parStruct)
{
...//处理函数执行完毕就会执行这个回调函数,可以通过parStruct获取需要的数据当然这个回调就是在原线程中了执行,而不是新建的线程了。
}
就大致是这样了,只是做了一个方便的处理。、
对了这里还有一个就是模板类里面这些个模板函数,是不好分开来h,cpp定义的,因为引用一段话:在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会实例化出来,所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码,于是连接器也黔驴技穷了。