一种通用异步任务执行方法及装置与流程

文档序号:16917269发布日期:2019-02-19 19:04阅读:183来源:国知局
一种通用异步任务执行方法及装置与流程

本发明涉及一种异步任务的执行方法及装置。



背景技术:

一个计算机系统处理外部请求的方式主要有两种:同步处理和异步处理。

两种处理方式分别有各自的好处,本申请主要针对异步处理,由于请求被转换为了消息,消息转换并缓存后请求操作即结束,不再需要任何等待,系统内部会进一步对各个消息进行处理,等处理完以后再通过回调通知的方式告知请求者处理结果。

现有任务执行类线程大多与具体任务关联,没有把线程和任务完全的抽象出来。大多数设计的异步任务线程都是“专用”线程,线程设计时只考虑执行“某一种”任务,外部需要提供的是执行任务所需的各种数据或者信息,然后通知到异步任务线程,待其执行。线程的功能有很大的局限性,每次有不同类型的“任务”都要重新设计线程,使得开发工作有许多重复。

现有异步任务执行线程大多是单独工作的,没有有效利用多线程来提高任务执行的并行性。



技术实现要素:

本发明针对现有异步任务线程类和具体任务耦合的问题,将具体任务封装为通用任务,可为任意的异步任务执行提供可能。

本发明的技术方案是:

一种通用异步任务执行方法,包括:将异步任务封装为通用任务,并将所述通用任务加入任务队列;

调用任务执行线程,并利用所述任务执行线程从所述任务队列中取出所述通用任务;

解开所述通用任务的封装,获得所述异步任务,并利用所述任务执行线程执行所述异步任务;

所述执行所述异步任务完毕之后,将所述任务队列中的所述通用任务消除。。

为所述异步任务创建一个任务观察者标识并封装为通用标识,所述任务观察者标识为未执行时通知任务队列将通用任务加入任务队列,当任务观察者标识收到执行结果后将任务从任务队列中删除。

任务执行线程运行时,从任务队列队首获取通用任务,然后再执行通用任务对应的异步任务。

任务执行线程终止运行时,任务队列不再接收新的异步任务,任务执行线程将已在队列中的任务执行完毕后再终止运行。

异步任务封装进任务队列时选择排在队首或者队尾。

异步任务封装后先进入分配队列,分配队列寻找任务队列为空的任务执行线程进行任务分配。

分配队列取出队首的通用任务,寻找任务队列为空的线程,将通用任务分配到该线程,如果没有任务队列为空的线程将任务放回队首。

当任务执行线程的任务队列为空时,发出待接收任务的信号,分配队列将队首的任务分配给该线程。

分配队列中的通用任务有一个分配任务观察者标识,分配任务观察者标识为已分配时将通用任务从分配队列中消除。

一种通用异步任务执行装置,包括:

通用任务单元,用于封装异步任务为通用任务;

通用观察者单元,用于封装异步任务的执行任务观察者标识,执行任务观察者标识表达异步任务的执行状态;

任务执行线程单元,包含任务队列和执行线程,任务队列用于存储通用任务并排序,执行线程用于执行任务队列中的任务,并将执行结果发给通用观察者单元;

通用任务单元包含一个指向通用观察者单元的指针,任务执行线程单元调用所述指针将所述通用任务单元的执行结果发送给所述通用观察者单元。。

任务执行线程单元还包含一个添加任务单元,添加任务单元用于将通用任务单元添加到任务队列。

若异步任务封装为通用任务单元时存在优先任务观察者标识,则执行任务单元将该通用任务添加到任务队列的队首,否则添加到队尾。

通用观察者单元中封装的执行任务观察者标识通过指针得到异步任务的执行结果后,将通用任务从任务队列中删除。

还包括分配队列单元,用于存储和排列封装后的通用任务,将通用任务分配给任务队列为空的任务执行线程单元。

分配队列单元中包含分配模块,分配模块用于取出队首的任务,找到任务队列为空的任务执行线程单元,将任务分配到该任务执行线程单元,没有找到则将任务放回队首。

当任务执行线程单元的任务队列为空时,发出待接收任务的信号,分配队列单元将队首的任务分配给该任务执行线程单元。

分配队列单元将通用任务进行二次封装,并添加一个分配任务观察者标识,分配任务观察者标识为未分配时通用任务在分配队列中排队等待分派,分配任务观察者标识为已分配时将通用任务踢出分配队列。

本发明的有益效果:

本发明设计的“通用”异步任务线程完全把具体的“任务”内容抽象出来,“任何”的“通用任务”都可以交给此线程完成,以后如有不同的任务,只需要把任务的内容封装进“通用任务”对象的task函数接口内部即可,不用重新设计线程。

异步任务线程可以把任意的“任务”抛到该线程去执行,通过合理的封装,异步任务线程在执行任务前,不管要执行的是什么任务,只知道有一个任务在等待执行,同时任务观察者也不管任务什么时候执行完成,只知道这个任务已经分配到队列,通过这种方式,避免了异步任务线程类和具体任务的耦合,可以适用于不同类型的异步任务,任何类型的异步任务都可以通过本发明的异步任务执行方法进行执行。

通过虚函数,在调用不同的派生类的时候,可以拥有不同的功能。当不同的子类继承这个父类的时候,定义不同的实现方法,那么实例化这个子类的时候,这个纯虚函数就有了不同的方法,不用将所有的代码都要重新复制一遍,当继承类越来越多的时候更能体现本方法的优势,简化了编程使得面向对象的方法更加灵活。

同时,通过对执行具体任务的异步任务线程的空闲情况的查询,可以设计出一个异步任务执行线程池,以提高任务执行的并行性。

附图说明:

图1为本发明的异步任务执行过程。

图2为本发明的装置结构组成示意图。

图3为本发明异步任务执行线程池的工作原理图。

具体实施方式

下面结合说明书附图对本发明的具体实现方式做一详细描述。

图1是本发明异步任务执行流程图。本发明的异步任务执行方法如下:

步骤s101:将异步任务封装为通用任务并加入任务队列。在本步骤中,实际上是定义了一个通用的任务,在这个通用的任务下面可以重新设置不同类型的子类任务,从通用任务可以派生出各种子类的具体任务方法。

步骤s102:任务执行线程从任务队列取出通用任务;任务执行线程在取出通用任务前并不知道这个任务是怎么执行的,这里的通用任务其实只是一个壳,只有调用壳下面的具体任务才能知道这个任务如何执行,这样就避免了线程和具体任务的耦合。

步骤s103:解开封装执行异步任务;任务执行线程调用通用任务下派生的子类任务,执行其方法步骤。

步骤s104:将执行完毕的任务从任务队列中消除。

进一步的,在步骤s101中,为异步任务创建一个任务观察者标识并封装为通用标识,任务观察者标识用于任务观察者标识任务的执行状态,并用于通知任务队列将未执行的通用任务加入任务队列,被封装的任务观察者标识还有另一个功能:在步骤s104中,任务观察者标识收到执行结果后将任务从任务队列中删除。具体过程如图1:

当有新的通用任务需要执行时,先创建该通用任务的任务观察者标识,任务观察者标识用于创建任务对象并且调用任务添加方法addtask,将通用任务排到任务队列中去,任务执行线程从任务队列中取出通用任务获得任务对象,执行通用任务下封装的子类异步任务,然后调用通用任务中的指针将执行结果发给通用标识,通用标识下封装的子类任务观察者标识得到执行结果后,将通用任务从任务队列中删除。通过这种方法,任务执行线程的所执行的对象是通用任务及通用标识,通用任务下可以有多种不同类型的实例化异步任务,相应的在通用标识下也可以有多种不用类型的实例化任务观察者标识。

进一步的,在步骤s102中,任务执行线程运行时,会不断的从任务队列队首获取通用任务,然后再执行通用任务对应的异步任务,当该任务执行线程想要停止工作时,其任务队列不再接收新的异步任务,任务执行线程将已在队列中的任务执行完毕后再终止运行。

进一步的,在步骤s101,通用任务在进入任务队列时可以选择是排在队首还是队尾,即根据用户的任务请求来确定该任务是否优先执行。

进一步的,为了提高任务执行的并行性,对于有多个任务执行线程时,将这些任务执行线程归为一个线程池,同时再创建一个任务分配队列从线程池中选择线程进行分配,具体的分配方法如下:

s001:异步任务封装后先进入分配队列并按照顺序排队,在这个步骤中异步任务会被再作一次封装,并添加一个分配标识,分配标识为未分配时通用任务在分配队列中排队等待分派。

s002:分配队列取出队首的通用任务,从线程池中寻找空的线程。同时当任务执行线程的任务队列为空时,会发出待接收任务的信号,根据待接受任务的信号寻找任务队列为空的线程,将通用任务分配到该线程,如果没有任务队列为空的线程将任务放回队首,等待下一次寻找。

s003:将分配成功的任务从分配队列中删除,分配队列中的通用任务有一个分配标识,将该通用任务放入任务队列后,将该分配标识转换成已分配标识;之后,根据分配标识,将已分配的通用任务删除。

本通用异步任务执行装置,主要包括计算机及各个功能单元,各个功能单元通过计算机程序实现,功能单元的结构如图2,由通用任务类、通用观察者类和通用异步任务执行线程类三种功能单元组成:

通用任务单元,用于封装异步任务为通用任务;在计算机程序中这部分是一个纯虚的函数basetask,通用任务类伪代码如下:

classbasetask{

public:

virtualvoid*task()=0;

public:

baseobserver*observer;

}

通用观察者单元,用于封装异步任务的任务观察者标识,任务观察者标识表达异步任务的执行状态;在计算机程序中这部分是一个纯虚的函数baseobserver。通用观察者类伪代码如下:

classbaseobserver{

public:

virtualvoidontaskexecuted(basetask*task,void*result)=0;

}

通用任务单元包含一个指向通用观察者单元的指针,通用观察者单元通过这个指针得到通用任务单元的执行状态。即通用任务除了纯虚函数basetask以外还有一个属性observer,是一个通用观察者类的指针,当task函数被执行获得结果后,如果observer不为空,异步任务执行线程会调用observer的ontaskexecuted方法来通知“观察者”任务执行的结果。

通过对basetask和baseobserver类的实例化,可实现具体的任务和观察者的处理。任务的执行和通知都有了承载者后,我们设计了一个异步任务执行线程类来执行任务:

任务执行线程单元,包含任务队列和执行线程,任务队列用于存储通用任务并排序,执行线程用于执行任务队列中的任务,并将执行结果发给通用观察者单元;若异步任务封装为通用任务单元时存在优先标识,则执行任务单元将该通用任务添加到任务队列的队首,否则添加到队尾。

进一步的,任务执行线程单元还包含一个添加任务单元addtask,添加任务单元用于将通用任务单元添加到任务队列。当有新的任务请求时,通用观察者单元中的任务观察者标识调用任务执行线程类(简称线程类)的addtask方法把任务添加到队列中,并向任务执行线程(简称线程)发送一个信号量,告知其有一个任务待处理。而线程启动后就进入等待状态,直到收到有任务待处理的通知后开始从任务队列头部取出任务、执行其task方法,并把返回的结果通过调用其observer的ontaskexecuted方法来通知“观察者”。

异步任务执行线程类伪代码如下:

classasynctaskexecutethread{

public:

voidaddtask(basetask*task,boolurgent){

if(!_running){

return;

}

if(urgent){

_taskarray.pushfront(task);

}else{

_taskarray.pushback(task);

}

_signal.signal();

}

protected:

voidrun(){

basetask*curtask=0;

void*result=0;

while(_running){

_signal.wait();

if(curtask=_taskarray.getfront()){

result=curtask->task();

if(curtask->observer){

curtask->observer->ontaskexecuted(curtask,result);

}

}

}

while(curtask=_taskarray.getfront()){

result=curtask->task();

if(curtask->observer){

curtask->observer->ontaskexecuted(curtask,result);

}

}

}

其中run函数为线程执行函数,_running为线程运行标识,当该标识为true时,线程会不断接收新的“任务”,并执行已在队列中的“任务”,当该标识为false时,标识该线程对象即将停止工作,这时会把已在队列中的“任务”都执行完然后退出线程。有任务要异步执行时通过addtask方法把任务添加到任务队列,可以指定urgent参数为true,任务会被添加到当前队列的顶端,会被优先执行,反之如果urgent参数为false,会把任务添加到队列的末尾,任务会等队列中的其他任务都执行完后再执行。

进一步的,举一个basetask和baseobserver类的实例化实例:

一个视频录像服务器需要把实时视频数据存储到文件里,而把数据写入文件是比较耗时的磁盘i/o操作,如果正在接收数据的线程去写入文件,那么可能造成数据接收不及时,或者数据丢失,或者阻塞网络i/o。这时就需要把写入文件的“操作”封装成一个异步任务,通过异步任务执行线程去执行。具体的操作如下:

假设录像类的任务观察者标识叫record,它继承了baseobserver类,实现了ontaskexecuted方法。我们设计一个异步任务叫videoframewritetask,它继承自basetask类,实现了task方法。在task方法中,会把数据写到视频文件里。假设我们已经有一个record类的实例和一个asynctaskexecutethread类的实例在运行,record类的实例有个叫writevideoframetofile的方法,通过调用该方法把要写入的视频数据以及要写入的文件的句柄传入,该方法内会创建一个videoframewritetask实例,并调用asynctaskexecutethread类实例的addtask方法把其放入任务队列“_taskarray”。之后,asynctaskexecutethread类实例的run方法就会从“taskarray”里取到videoframewritetask类的实例,并执行其task方法,获得返回值,然后再通过调用它的observer属性(该属性其实就指向record类实例的指针)的ontaskexecuted方法来通知record类的实例任务执行的结果。当record类实例收到ontaskexecuted回调时会把videoframewritetask类的实例释放掉。

videoframewritetask类和record类的定义如下:

classvideoframewritetask:publicbasetask{

public:

videoframewritetask(videofilehandler*filehandler,videoframe*videoframe){

writingfilehandler=filehandler;

_videoframe=videoframe->copy();

}

virtualvoid*task(){

writingfilehandler->writevideoframe(_videoframe);

_videoframe->release();

returnnull;

};

private:

videofilehandler*_writingfilehandler;

videoframe*_videoframe;

}

classrecord:publicbaseobserver{

private:

asynctaskexecutethread*_asynctaskthread;

public:

voidwritevideoframetofile(videofilehander*fh,videoframe*videoframe){

basetask*writeframetask=newvideoframewritetask(fh,videoframe);

_asynctaskthread->addtask(writeframetask);

}

virtualvoidontaskexecuted(basetask*task,void*result){

deletetask;

};

}

如图3:还包括分配队列单元,用于存储和排列封装后的通用任务,将通用任务分配给任务队列为空的任务执行线程单元。

分配队列单元中包含分配模块,分配模块用于取出队首的任务,找到任务队列为空的任务执行线程单元,将任务分配到该任务执行线程单元,没有找到则将任务放回队首。

当任务执行线程单元的任务队列为空时,发出待接收任务的信号,分配队列单元将队首的任务分配给该任务执行线程单元。

分配队列单元将通用任务进行二次封装,并添加一个分配标识,分配标识为未分配时通用任务在分配队列中排队等待分派,将该通用任务放入任务队列后,将该分配标识转换成已分配;之后,根据分配标识,将已分配的通用任务删除。

我们知道异步执行的任务往往都是比较耗时的任务,因此排在异步任务执行队列里等待的时间也往往较长。如果希望异步任务能更及时地被执行,我们可以创建一组异步任务执行线程对象,再用一个专门用于管理异步任务执行线程分配的对象把“把具体任务分配到最合适的异步任务执行线程”去。具体地,我们可以这么做:

1:首先我们把asynctaskexecutethread扩展一下,给它添加一个叫addtaskifidle的方法,如果现在其任务队列为空,那么就把任务添加到任务队列里(末端),并返回true,否则返回false,用于在空闲状态接收任务:

classasyntaskexecutethreadex:publicasynctaskexecutethread{

public:

booladdtaskifidle(basetask*task){

if(_taskarray.size()==0){

addtask(task,false);

returntrue;

}else{

returnfalse;

}

}

}

2:我们根据需要创建一组asyntaskexecutethreadex对象,作为异步任务执行线程池。我们把这组asyntaskexecutethreadex对象称为threadpool。

3:再派生一个asyntaskexecutethread和baseobserver的子类assigntaskthread,该线程类专门用于把“具体”任务分配到空闲的线程去。我们把这个类的实例称为assigntaskthread。这时我们还需要一个抽象的“分配任务的任务”类,我们定义如下:

classassigntasktask:publicbasetask{

public:

assigntasktask(basetask*realtask,array<asyntaskexecutethreadex*>*threadpool){

_realtask=realtask;

_threadpool=threadpool;

}

virtualvoid*task(){

boolassigned=false

for(asyntaskexecutethreadex*threadinthreadpool){

if(thread->addtaskifidle(_realtask)){

assigned=true;

break;

}

}

if(!assigned){

returnnull;

}

returnthis;

};

private:

basetask*_realtask;

array<asyntaskexecutethreadex*>*_threadpool;

}

4当assigntaskthread的run函数执行完assigntasktask对象的task方法后,会通过ontaskexecuted回调通知到assigntaskthread本身,如果执行结果为null表示没有分配好执行线程,如果不为空表示具体任务已被分配到具体执行线程。assigntaskthread的ontaskexecuted函数需要实现如下:

virtualvoidontaskexecuted(basetask*task,void*result){

if(!result){

addtask(task,true);

}else{

deletetask;

}

};

即当任务未被分配到具体执行线程时,立即把分配任务的任务添加到任务队列的头部。

5当我们接收到一个具体的任务时,我们先把这个任务进行封装,封装到一个“分配任务的任务”的对象里,然后调用assigntaskthread的addtask放到其任务队列里。打个比方,我们现在要执行“数据库写入任务”,那么我们在创建好dblogwritetask对象时,我们把这个对象再进行一次封装:

assigntasktask*assigntasktask=newassigntasktask(dblogwritetask,threadpool);

(其中,dblogwritetask是dblogwritetask对象)然后把这个封装后的任务对象添加到assigntaskthread的任务队列里:

assigntaskthread->addtask(assigntasktask,false);

之后assigntaskthread的run函数的循环在取到assigntasktask时就会通过调用它的task方法去尝试把dblogwritetask分配到具体的执行线程去,如果没有找到空闲的线程,那么会通过ontaskexecuted反馈给assigntasktask自己,assigntasktask在ontaskexecuted里发现dblogwritetask没有被分配,立即又把assigntasktask添加到自己的任务队列的头部,等待下一次run函数的循环继续尝试分配。

由于assigntaskthread总是尝试找到空闲的那个线程,所以会保证通过其addtask方法接收的“具体任务”被最快的执行。

当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1