python利用ThreadPoolExecutor实现有任务异常,就终止线程池中的所有剩余任务
先描述一下场景:
我有一批任务需要放入线程池中去处理,但是一旦线程池中有1个任务出现了异常(抛了Exception)就将线程中尚未开始的任务全部取消不执行。
需要说明的是正在执行的任务因为无法撤销,所以正在执行的任务只能继续执行,等他执行完成。
import queue from concurrent.futures import ThreadPoolExecutor, as_completed, wait, ALL_COMPLETED, FIRST_EXCEPTION def send_cmd(ip, exec_queue): # 如果消息队列中消息不为空,说明已经有任务异常了 if not exec_queue.empty(): return try: # 需要执行的主任务 except Exception as e: # 如果任务异常了就在队列中写入一个消息,用于锁住线程池 exec_queue.put("Termination") # 此处一定要将异常再次抛出,否则主线程池无法捕获异常,会统一认定为任务已被取消 raise Exception(e) # 此处使用消息队列作为线程池锁,避免在第一个任务异常发生后到主线程获知中间仍然有任务被发送执行 exec_queue = queue.Queue() with ThreadPoolExecutor(max_workers=thread_pool_size) as executor: task_dict, task_list = {}, [] # 将任务全部放入线程池中 for ip in ip_list: task = executor.submit(send_cmd, ip, exec_queue) task_dict[task] = ip task_list.append(task) # 等待第一个任务抛出异常,就阻塞线程池 wait(task_list, return_when=FIRST_EXCEPTION) # 反向序列化之前塞入的任务队列,并逐个取消 for task in reversed(task_list): task.cancel() # 等待正在执行任务执行完成 wait(task_list, return_when=ALL_COMPLETED) for task in task_list: if task_dict.get(task): if "finished returned NoneType" in str(task) or task.cancelled(): print("{}被取消".format(task_dict.get(task))) elif "finished raised Exception" in str(task): print("{}执行异常".format(task_dict.get(task))) else: print("{}执行成功".format(task_dict.get(task)))
加入队列作为线程池锁,是因为在实际测试中发现,如果所有任务被一次性塞入线程池后,当第一个异常发生到,异常被主线程池捕获中间,仍然会有任务被执行,具体数量依据任务执行的快慢,数量也是不定的,但至少会有1个,属于必现情况。加入线程池,可以有效阻止这类问题的发生。