Files
work-secretfile-selfcheck/UmiOCR-data/py_src/mission/mission.py

303 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ==============================================
# =============== 任务管理器 基类 ===============
# ==============================================
from PySide2.QtCore import QMutex, QRunnable
from threading import Condition
from uuid import uuid4 # 唯一ID
import time
from umi_log import logger
from ..utils.thread_pool import threadRun # 异步执行函数
class Mission:
def __init__(self):
self._msnInfoDict = {} # 任务信息的字典
self._msnListDict = {} # 任务队列的字典
self._msnPausedDict = {} # 已暂停的任务队列
self._msnMutex = QMutex() # 任务队列的锁
self._task = None # 异步任务对象
self._taskMutex = QMutex() # 任务对象的锁
# 任务队列调度方式
# 1111 : 轮询调度轮流取每个队列的第1个任务
# 1234 : 顺序调度,将首个队列所有任务处理完,再进入下一个队列
self._schedulingMode = "1111"
# ========================= 【调用接口】 =========================
"""
添加任务队列的格式
mission = {
"onStart": 任务队列开始回调函数 , (msnInfo)
"onReady": 一项任务准备开始 , (msnInfo, msn)
"onGet": 一项任务获取结果 , (msnInfo, msn, res)
"onEnd": 任务队列结束 , (msnInfo, msg) // msg可选前缀 [Success] [Warning] [Error]
}
MissionOCR.addMissionList(mission, paths)
"""
# 【异步】添加一条任务队列。成功返回任务ID失败返回 startswith("[Error]")
# msnInfo: { 回调函数 "onStart", "onReady", "onGet", "onEnd"}
# msnList: [ 任务1, 任务2 ]
def addMissionList(self, msnInfo, msnList):
if len(msnList) < 1:
return "[Error] no valid mission in msnList!"
msnID = str(uuid4())
# 检查并补充回调函数
# 队列开始,单个任务准备开始,单任务取得结果,队列结束
cbKeys = ["onStart", "onReady", "onGet", "onEnd"]
for k in cbKeys:
if k not in msnInfo or not callable(msnInfo[k]):
msnInfo[k] = lambda *e: None
# 任务状态state: waiting 等待开始, running 进行中, stop 要求停止
msnInfo["state"] = "waiting"
msnInfo["msnID"] = msnID
# 添加到任务队列
self._msnMutex.lock() # 上锁
self._msnInfoDict[msnID] = msnInfo # 添加任务信息
self._msnListDict[msnID] = msnList # 添加任务队列
self._msnMutex.unlock() # 解锁
# 启动任务
self._startMsns()
# 返回任务id
return msnID
# 停止一些任务队列
def stopMissionList(self, msnIDs):
if not isinstance(msnIDs, list):
msnIDs = [msnIDs]
self._msnMutex.lock() # 上锁
for msnID in msnIDs:
# 将暂停中的任务恢复
if msnID in self._msnPausedDict:
info, list_ = self._msnPausedDict[msnID]
self._msnInfoDict[msnID] = info
self._msnListDict[msnID] = list_
# 将进行中的任务置为停止状态
if msnID in self._msnListDict:
self._msnInfoDict[msnID]["state"] = "stop" # 设为停止状态
self._msnMutex.unlock() # 解锁
self._startMsns() # 拉起工作线程,使已暂停的任务可以正常结束
# 停止全部任务
def stopAllMissions(self):
self._msnMutex.lock() # 上锁
# 将暂停中的任务恢复
for msnID in self._msnPausedDict:
info, list_ = self._msnPausedDict[msnID]
self._msnInfoDict[msnID] = info
self._msnListDict[msnID] = list_
# 将进行中的任务置为停止状态
for msnID in self._msnListDict:
self._msnInfoDict[msnID]["state"] = "stop"
self._msnMutex.unlock() # 解锁
self._startMsns()
# 暂停一些任务队列
def pauseMissionList(self, msnIDs):
if not isinstance(msnIDs, list):
msnIDs = [msnIDs]
self._msnMutex.lock() # 上锁
for msnID in msnIDs:
if msnID in self._msnListDict:
msn = (self._msnInfoDict[msnID], self._msnListDict[msnID])
self._msnPausedDict[msnID] = msn
del self._msnInfoDict[msnID]
del self._msnListDict[msnID]
self._msnMutex.unlock() # 解锁
logger.debug(f"任务暂停: {msnID}")
# 恢复一些任务队列的运行
def resumeMissionList(self, msnIDs):
if not isinstance(msnIDs, list):
msnIDs = [msnIDs]
self._msnMutex.lock() # 上锁
for msnID in msnIDs:
if msnID in self._msnPausedDict:
info, list_ = self._msnPausedDict[msnID]
self._msnInfoDict[msnID] = info
self._msnListDict[msnID] = list_
del self._msnPausedDict[msnID]
self._msnMutex.unlock() # 解锁
self._startMsns() # 拉起工作线程
logger.debug(f"任务恢复: {msnID}")
# 获取每一条任务队列长度
def getMissionListsLength(self):
lenDict = {}
self._msnMutex.lock()
for k in self._msnListDict:
lenDict[str(k)] = len(self._msnListDict[k])
self._msnMutex.unlock()
return lenDict
# 【同步】添加一个任务或队列,等待完成,返回任务结果列表。[i]["result"]为结果
def addMissionWait(self, argd, msnList):
if not isinstance(msnList, list):
msnList = [msnList]
resList = msnList[:] # 浅拷贝出一条结果列表
nowIndex = 0 # 当前处理的任务
msnLen = len(msnList)
condition = Condition() # 线程同步器
endMsg = "" # 任务结束的消息
def _onGet(msnInfo, msn, res):
nonlocal nowIndex
resList[nowIndex]["result"] = res
nowIndex += 1
def _onEnd(msnInfo, msg):
nonlocal endMsg
endMsg = msg
with condition: # 释放线程阻塞
condition.notify()
def _pass(*x):
pass
msnInfo = {
"onStart": _pass,
"onReady": _pass,
"onGet": _onGet,
"onEnd": _onEnd,
"argd": argd,
}
msnID = self.addMissionList(msnInfo, msnList)
if msnID.startswith("[Error]"): # 添加任务失败
endMsg = msnID
else: # 添加成功,线程阻塞,直到任务完成。
with condition:
condition.wait()
# 补充未完成的任务
for i in range(nowIndex, msnLen):
if "result" not in resList[i]:
resList[i]["result"] = {"code": 803, "data": f"任务提前结束。{endMsg}"}
return resList
# ========================= 【主线程 方法】 =========================
def _startMsns(self): # 启动异步任务,执行所有任务列表
# 若当前异步任务对象为空,则创建工作线程
self._taskMutex.lock() # 上锁
if self._task == None:
self._task = threadRun(self._taskRun)
self._taskMutex.unlock() # 解锁
# ========================= 【子线程 方法】 =========================
def _taskRun(self): # 异步执行任务字典的流程
dictIndex = 0 # 当前取任务字典中的第几个任务队列
# 循环,直到任务队列的列表为空
while True:
# 1. 检查api和任务字典是否为空
self._msnMutex.lock() # 锁1 上锁
dl = len(self._msnInfoDict) # 任务字典长度
if dl == 0: # 任务字典已空
self._msnMutex.unlock() # 锁1 解锁
break
# 2. 任务调度,取一个任务
if self._schedulingMode == "1111": # 轮询
dictIndex = (dictIndex + 1) % dl
elif self._schedulingMode == "1234": # 顺序
dictIndex = 0 # 始终为首个队列
dictKey = tuple(self._msnInfoDict.keys())[dictIndex]
msnInfo = self._msnInfoDict[dictKey]
msnList = self._msnListDict[dictKey]
self._msnMutex.unlock() # 锁1 解锁
# 3. 检查任务是否要求停止
if msnInfo["state"] == "stop":
self._msnDictDel(dictKey)
msnInfo["onEnd"](msnInfo, "[Warning] Task stop.")
continue
# 4. 前处理,检查、更新参数
preFlag = self.msnPreTask(msnInfo)
if preFlag == "continue": # 跳过本次
logger.debug(f"任务跳过: {dictKey}")
continue
elif preFlag.startswith("[Error]"): # 异常,结束该队列
msnInfo["onEnd"](msnInfo, preFlag)
self._msnDictDel(dictKey)
dictIndex -= 1 # 字典下标回退1位下次执行正确的下一项
continue
# 5. 首次任务
if msnInfo["state"] == "waiting":
msnInfo["state"] = "running"
msnInfo["onStart"](msnInfo)
# 6. 执行任务,并记录时间
msn = msnList[0]
msnInfo["onReady"](msnInfo, msn)
t1 = time.time()
res = self.msnTask(msnInfo, msn)
t2 = time.time()
if isinstance(res, dict): # 补充耗时和时间戳
res["time"] = t2 - t1
res["timestamp"] = t2
# 7. 再次检查任务是否要求停止,或者已暂停
self._msnMutex.lock() # 锁2 上锁
if msnInfo["state"] == "stop":
self._msnDictDel(dictKey)
self._msnMutex.unlock() # 锁2 解锁
msnInfo["onEnd"](msnInfo, "[Warning] Task stop.")
continue
if dictKey not in self._msnInfoDict:
self._msnMutex.unlock() # 锁2 解锁
continue
# 8. 不停止,则上报该任务
msnList.pop(0) # 弹出该任务
self._msnMutex.unlock() # 锁2 解锁
# 回调。注意:回调函数执行时间长时,可能用户再次提交了任务暂停,需要后续继续判断。
msnInfo["onGet"](msnInfo, msn, res)
# 9. 这条任务队列完成
if len(msnList) == 0:
msnInfo["onEnd"](msnInfo, "[Success]")
self._msnMutex.lock() # 锁3 上锁
self._msnDictDel(dictKey)
self._msnMutex.unlock() # 锁3 解锁
dictIndex -= 1 # 字典下标回退1位下次执行正确的下一项
# 完成
self._taskFinish()
def _msnDictDel(self, dictKey): # 停止一组任务队列
# 正常 删除任务队列项
if dictKey in self._msnInfoDict:
del self._msnInfoDict[dictKey]
del self._msnListDict[dictKey]
# 如果该任务在暂停中,则移除暂停队列中的项
if dictKey in self._msnPausedDict:
del self._msnPausedDict[dictKey]
logger.debug(f"移除暂停任务: {dictKey}")
def _taskFinish(self): # 任务结束
self._taskMutex.lock() # 上锁
self._task = None
self._taskMutex.unlock() # 解锁
# ========================= 【继承重载】 =========================
def msnPreTask(self, msnInfo): # 任务前处理用于更新api和参数。
"""返回值可选:
"" :空字符串表示正常继续。
"continue" :跳过本次任务
"[Error] xxxx" :终止这条任务队列,返回异常信息
"""
# return "[Error] No overloaded msnPreTask. \n【异常】未重载msnPreTask。"
return ""
def msnTask(self, msnInfo, msn): # 执行任务msn返回结果字典。
logger.debug("mission 未重载 msnTask")
return {"error": "[Error] No overloaded msnTask. \n【异常】未重载msnTask。"}
def getStatus(self): # 返回当前状态
return "Mission 基类 返回空状态"