Package: onecat-task-queue
又是造轮子系列咯，Python上有很多成熟完善的异步任务队列框架可以用，比如Celery，或者RQ，不过这些都不自带消息队列服务，都需要使用Redis、RabbitMQ之类的消息队列才行，我用到小项目中又不需要附带这么多东西，于是自己动手来实现咯。思路将需要异步执行的任务添加到队列自动从队列中取出任务，创建新线程执行保存任务的执行结果和输出任务完成，调用回调函数，处理返回的数据使用输出重定向处理任务的输出Demo附上使用任务队列的demo代码：if__name__=='__main__':task_queue=TaskQueue(output_redirect=True)deffun1():time.sleep(2)return1deffun2():time.sleep(3)return2task_queue.put(Task(func=fun1,callback=lambdatask,result:print(f'task1 result:{result}'),))task_queue.put(Task(func=fun2,callback=lambdatask,result:print(f'task2 result:{result}'),))defcustom_output(msg):print(f'[custom]{msg}')deffun3(num1,num2):print(f'num1={num1}')print(f'num2={num2}')returnnum1+num2defcallback(task_obj,result):print(f'task3 result={result}')output=task_obj.outputsoutput.custom=custom_outputoutput.reset()output.to_custom()task_queue.put(Task(func=fun3,callback=callback,args=[2,3]))print('task queue run')task_queue.run()print('do other things...')foriinrange(0,100):print(i*i)wheel构建python3.6setup.pysdistbdist_wheel
twineuploaddist/*-u"用户名"-p"密码"下面分析一下几个关键部分的代码实现优先级队列Python标准库已经有实现好的优先级队列了，但是当在相同优先级下传入同样的对象时，他会自动比较这些对象，不过我们的Task类没有实现相关的运算符重载，所以无法比较。要解决这个问题，要不就在Task里面实现运算符重载，要不就是自己实现一个优先级队列，我选择后者。classPriorityQueue:"""自己实现的优先级队列，使用了Python的堆"""def__init__(self):self._index=0self.queue=[]defpush(self,priority,val):heapq.heappush(self.queue,(priority,self._index,val))self._index+=1defpop(self):returnheapq.heappop(self.queue)[-1]@propertydefempty(self):returnlen(self.queue)==0输出重定向其实这个挺坑的了，毕竟是异步任务队列，难免涉及到线程的竞争问题，又没办法单独控制每个任务的输出，不过我还是做了，小项目的话还是可以用的（其实就是懒得移植一下已有代码）代码如下，同样是使用队列来实现输出缓冲区，大小可以自己调整，默认支持输出到控制台、文件或者返回列表。一般重定向输出的话需要自己实现可以输出处理函数，直接给Redirection类的custom 属性赋值即可。classRedirection:def__init__(self,buffer_size=512):self.buffer=Queue(maxsize=512)self._console=sys.stdout# 自定义的输出端self.custom=Nonedefwrite(self,output_stream):# 加入缓冲区队列self.buffer.put(output_stream)defto_console(self):sys.stdout=self._console# 出列whilenotself.buffer.empty():print(self.buffer.get())defto_file(self,file_path):withopen(file_path,'w+')asf:sys.stdout=fwhilenotself.buffer.empty():print(self.buffer.get())f.close()defto_custom(self):whilenotself.buffer.empty():self.custom(self.buffer.get())defto_list(self):data=[]whilenotself.buffer.empty():data.append(self.buffer.get())returndatadefflush(self):self.buffer.empty()defreset(self):sys.stdout=self._consoleTask类没什么好说的，定义任务类，Task，代码如下：classTask:def__init__(self,func,callback=None,priority=Priority.MIDDLE,args=(),kwargs={}):"""Args:func: 需要执行的函数callback:  执行完的回调函数priority: 优先级*args:**kwargs:"""self._id=uuid.uuid4().hexself.function=funcself.callback=callbackself.priority=priorityself.args=argsself.kwargs=kwargs# 任务运行过程的输出，stdout的输出self._outputs:Redirection=None@propertydefid(self):returnself._id@propertydefoutputs(self)->Redirection:returnself._outputs@outputs.setterdefoutputs(self,value:Redirection):self._outputs=valuedefrun(self):try:ifself.callback:# 回调函数原型 callback(task_obj, result)result=self.callback(self,self.function(*self.args,**self.kwargs))else:result=self.function(*self.args,**self.kwargs)returnresultexceptExceptionase:ifself.callback:result=self.callback(self,e)else:result=ereturnresult使用起来很简单，如下：deffun1(num1,num2):print(f'num1={num1}')print(f'num2={num2}')returnnum1+num2Task(func=fun1,callback=lambdatask,result:print(f'task result:{result}'),)# 也可以写成这样Task(func=lambdanum1,num2:num1+num2,callback=lambdatask,result:print(f'task result:{result}'),args=[2,3])任务队列最后是实现任务队列，也很简单，根据任务的优先级进行调度即可。classTaskQueue:"""基于线程的异步任务队列todo 下次做一个基于进程的队列，充分利用多核CPU性能"""def__init__(self,output_redirect=False):self.queue=PriorityQueue()self.output_redirect=output_redirectself._redirect_objs={}self._results={}defput(self,task:Task):"""将task加入任务列表Args:task:Returns:返回task id"""self.queue.push(task.priority,task)returntask.iddefget(self):returnself.queue.pop()defrun(self):whilenotself.queue.empty:task=self.get()# 开启新线程t=threading.Thread(target=self._task_wrapper,name=task.id,args=[task])self._log(f'Start thread{task.id}')t.start()defget_output(self,task_id:str)->Redirection:returnself._redirect_objs.get(task_id,None)defget_result(self,task_id:str):returnself._results.get(task_id,None)@staticmethoddef_log(msg:str):"""日志输出接口，可以替换为日志组件"""print(f'[TaskQueue]{msg}')def_task_wrapper(self,task:Task):ifself.output_redirect:iftask.idinself._redirect_objs:redirect_obj=self._redirect_objs[task.id]else:redirect_obj=Redirection(2048)self._redirect_objs[task.id]=redirect_obj# 重定向输出sys.stdout=redirect_objtask.outputs=redirect_objresult=task.run()# 恢复默认输出redirect_obj.reset()self._log(f'Task finished.{task.id}')else:result=task.run()# 保存结果self._results[task.id]=result整个项目代码就在这了，很简单，也有很多不足的地方，不过小项目用用勉强还可以吧~过几天发布到pip，有需要的同学直接pip安装就可以使用，请关注博客更新。参考资料运算符重载https://blog.csdn.net/goodlixueyong/article/details/52589979https://www.open-open.com/code/view/1433410658322Celery异步任务队列https://www.cnblogs.com/StitchSun/p/8552488.htmlhttps://zhuanlan.zhihu.com/p/37637660欢迎交流我整理了一系列的技术文章和资料，在公众号「程序设计实验室」后台回复 linux、flutter、c#、netcore、android、kotlin、java、python 等可获取相关技术文章和资料，同时有任何问题都可以在公众号后台留言~