概要
最近工作中遇到了一项任务,将导出文件功能改成异步处理。目前情况是已完成,但是有一个巨大的bug困扰了我很长时间,仔细思考好像没办法解决。下面我将梳理我的开发流程,我是小白,大神勿喷。
异步任务处理设计
实现效果:前端发起导出任务请求后,后端生成任务id返回给前端,前端就可以继续其他操作,同时前端根据任务id每2秒中查询一次后端的任务是否完成,完成则获取处理结果
1. 前端设计
以下是一段导出相应时间内的数据表格请求设计。
sysExportLogs = () =>{
let { start, end } = this.state;
let task_id = '';
systemAdminAPI.sysAdminExportLogsExcel(start, end, logType).then(res => {
task_id = res.data.task_id;
return systemAdminAPI.queryAsyncOperationExportExcel(task_id);
}).then(res => {
if (res.data.is_finished === true){
location.href = 'sys/log/export-excel/?task_id=' + task_id;
} else {
this.timer = setInterval(() => {
systemAdminAPI.queryAsyncOperationExportExcel(task_id).then(res => {
if (res.data.is_finished === true){
this.setState({isFinished: true});
clearInterval(this.timer);
location.href = 'sys/log/export-excel/?task_id=' + task_id;
}
}).catch(err => {
if (this.state.isFinished === false){
clearInterval(this.timer);
toaster.danger(gettext('Failed to export. '));
}
});
}, 1000);
}
});
};
详解: 前端通过点击一个按钮,触动sysExportLogs函数,该函数先发送一个请求用来获取task_id,之后设置一个定时器,每秒发起一个请求,查询后端任务是否已完成,完成则清除定时器,未完成则持续发起请求查看完成状态。
2. 后端设计
class TaskManager(object):
def __init__(self):
self.tasks_map = {}
self.task_results_map = {}
self.tasks_queue = queue.Queue(10)
self.current_task_info = {}
self.threads = []
def is_valid_task_id(self, task_id):
return task_id in (self.tasks_map.keys() | self.task_results_map.keys())
def add_one_task(self, session, tstart, tend, log_type):
task_id = str(uuid.uuid4())
task =(handleTaskFunc, (session, tstart, tend, log_type, task_id))
self.tasks_queue.put(task_id)
self.tasks_map[task_id] = task
return task_id
def add_two_task(self, session, tstart, tend, log_type):
........
........
return task_id
def query_status(self, task_id):
task_result = self.task_results_map.pop(task_id, None)
if task_result == 'success':
return True, None
if isinstance(task_result, str) and task_result.startswith('error_'):
return True, task_result[6:]
return False, None
def threads_is_alive(self):
info = {}
for t in self.threads:
info[t.name] = t.is_alive()
return info
def handle_task(self):
while True:
try:
task_id = self.tasks_queue.get(timeout=2)
except queue.Empty:
continue
except Exception as e:
logger.error(e)
continue
task = self.tasks_map.get(task_id)
if type(task) != tuple or len(task) < 1:
continue
if type(task[0]).__name__ != 'function':
continue
task_info = task_id + ' ' + str(task[0])
try:
self.current_task_info[task_id] = task_info
logging.info('Run task: %s' % task_info)
start_time = time.time()
# run
task[0](*task[1])
self.task_results_map[task_id] = 'success'
finish_time = time.time()
logging.info('Run task success: %s cost %ds \n' % (task_info, int(finish_time - start_time)))
self.current_task_info.pop(task_id, None)
except Exception as e:
self.task_results_map[task_id] = 'error_' + str(e.args[0])
logger.exception(e)
logger.error('Failed to handle task %s, error: %s \n' % (task_info, e))
self.current_task_info.pop(task_id, None)
finally:
self.tasks_map.pop(task_id, None)
def run(self):
thread_num = 3
for i in range(thread_num):
t_name = 'TaskManager Thread-' + str(i)
t = threading.Thread(target=self.handle_task, name=t_name)
self.threads.append(t)
t.setDaemon(True)
t.start()
task_manager = TaskManager()
详解: 以上代码,设计了一个处理异步任务的类,在类中初始化了一个任务队列以及一些配合使用的映射。通过实例化这个类对象,运行线程run,持续对这个任务队列进行监听,当任务队列中存在任务时,对其进行后续操作,若没有任务,则持续进行监听。
小结
以上仅是大体的思路,在实现这个功能的时候,有一个致命的一点(对于我来说),我设计的类,在一个包中,里面的添加任务也在同一个包中,而启动监听却在另一个包中,这样会导致所实例化的对象并不是同一个,目前来说,无解。
问题:如何在两个不同的包中共享同一个实例化对象!