2026-01-22 16:54:38 +08:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
# @File : apsmodule.py
|
|
|
|
|
# @Date : 2021/2/26
|
|
|
|
|
# @Desc :
|
|
|
|
|
import threading
|
|
|
|
|
import time
|
|
|
|
|
import uuid
|
|
|
|
|
from enum import Enum
|
|
|
|
|
from typing import Optional, Callable, Dict, Any
|
|
|
|
|
|
|
|
|
|
from Lib.log import logger
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ThreadStatus(Enum):
|
|
|
|
|
PENDING = "pending"
|
|
|
|
|
RUNNING = "running"
|
|
|
|
|
COMPLETED = "completed"
|
|
|
|
|
FAILED = "failed"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ThreadInfo:
|
|
|
|
|
def __init__(self, thread_id: str, thread_obj: threading.Thread):
|
|
|
|
|
self.thread_id = thread_id
|
|
|
|
|
self.thread_obj = thread_obj
|
|
|
|
|
self.status = ThreadStatus.PENDING
|
|
|
|
|
self.start_time: Optional[float] = None
|
|
|
|
|
self.end_time: Optional[float] = None
|
|
|
|
|
self.result: Any = None
|
|
|
|
|
self.exception: Optional[Exception] = None
|
|
|
|
|
|
|
|
|
|
def get_duration(self) -> Optional[float]:
|
|
|
|
|
if self.start_time is None:
|
|
|
|
|
return None
|
|
|
|
|
end = self.end_time if self.end_time else time.time()
|
|
|
|
|
return end - self.start_time
|
|
|
|
|
|
|
|
|
|
def is_alive(self) -> bool:
|
|
|
|
|
return self.thread_obj.is_alive()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ThreadModuleManager:
|
|
|
|
|
def __init__(self, exception_handler: Optional[Callable[[str, Exception], None]] = None):
|
|
|
|
|
self._threads: Dict[str, ThreadInfo] = {}
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
self._exception_handler = exception_handler or self._default_exception_handler
|
|
|
|
|
self._next_auto_id = 0
|
|
|
|
|
|
|
|
|
|
def _default_exception_handler(self, thread_id: str, exception: Exception) -> None:
|
|
|
|
|
logger.error(f"[Thread {thread_id}] Exception occurred: {type(exception).__name__}")
|
|
|
|
|
logger.exception(exception)
|
|
|
|
|
|
|
|
|
|
def _generate_thread_id(self) -> str:
|
|
|
|
|
with self._lock:
|
|
|
|
|
self._next_auto_id += 1
|
|
|
|
|
return f"thread_{self._next_auto_id}"
|
|
|
|
|
|
|
|
|
|
def _run_task(self, thread_id: str, task_obj) -> None:
|
|
|
|
|
thread_info = self._threads[thread_id]
|
|
|
|
|
thread_info.status = ThreadStatus.RUNNING
|
|
|
|
|
thread_info.start_time = time.time()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
result = task_obj.execute()
|
|
|
|
|
thread_info.result = result
|
|
|
|
|
thread_info.status = ThreadStatus.COMPLETED
|
2026-01-27 02:04:07 +08:00
|
|
|
logger.info(f"[Thread {thread_id}] Task completed successfully.")
|
2026-01-22 16:54:38 +08:00
|
|
|
except Exception as e:
|
|
|
|
|
thread_info.exception = e
|
|
|
|
|
thread_info.status = ThreadStatus.FAILED
|
|
|
|
|
self._exception_handler(thread_id, e)
|
2026-01-27 02:04:07 +08:00
|
|
|
logger.error(f"[Thread {thread_id}] Task failed with exception.")
|
2026-01-22 16:54:38 +08:00
|
|
|
finally:
|
|
|
|
|
thread_info.end_time = time.time()
|
|
|
|
|
|
|
|
|
|
def start_task(self, task_obj, thread_id: Optional[str] = None) -> str:
|
|
|
|
|
if thread_id is None:
|
|
|
|
|
thread_id = str(uuid.uuid1())
|
|
|
|
|
|
|
|
|
|
thread = threading.Thread(
|
|
|
|
|
target=self._run_task,
|
|
|
|
|
args=(thread_id, task_obj),
|
|
|
|
|
name=thread_id,
|
|
|
|
|
daemon=False
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
thread_info = ThreadInfo(thread_id, thread)
|
|
|
|
|
self._threads[thread_id] = thread_info
|
|
|
|
|
|
|
|
|
|
thread.start()
|
|
|
|
|
return thread_id
|
|
|
|
|
|
|
|
|
|
def get_thread_status(self, thread_id: str) -> Optional[ThreadStatus]:
|
|
|
|
|
with self._lock:
|
|
|
|
|
thread_info = self._threads.get(thread_id)
|
|
|
|
|
if thread_info:
|
|
|
|
|
return thread_info.status
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_thread_info(self, thread_id: str) -> Optional[ThreadInfo]:
|
|
|
|
|
with self._lock:
|
|
|
|
|
return self._threads.get(thread_id)
|
|
|
|
|
|
|
|
|
|
def get_result(self, thread_id: str) -> Any:
|
|
|
|
|
with self._lock:
|
|
|
|
|
thread_info = self._threads.get(thread_id)
|
|
|
|
|
if thread_info and thread_info.status == ThreadStatus.COMPLETED:
|
|
|
|
|
return thread_info.result
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_exception(self, thread_id: str) -> Optional[Exception]:
|
|
|
|
|
with self._lock:
|
|
|
|
|
thread_info = self._threads.get(thread_id)
|
|
|
|
|
if thread_info:
|
|
|
|
|
return thread_info.exception
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def wait_for_thread(self, thread_id: str, timeout: Optional[float] = None) -> bool:
|
|
|
|
|
with self._lock:
|
|
|
|
|
thread_info = self._threads.get(thread_id)
|
|
|
|
|
if not thread_info:
|
|
|
|
|
return False
|
|
|
|
|
thread_obj = thread_info.thread_obj
|
|
|
|
|
|
|
|
|
|
thread_obj.join(timeout=timeout)
|
|
|
|
|
return not thread_obj.is_alive()
|
|
|
|
|
|
|
|
|
|
def wait_for_all_threads(self, timeout: Optional[float] = None) -> bool:
|
|
|
|
|
with self._lock:
|
|
|
|
|
threads_copy = list(self._threads.values())
|
|
|
|
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
for thread_info in threads_copy:
|
|
|
|
|
remaining_timeout = None
|
|
|
|
|
if timeout is not None:
|
|
|
|
|
elapsed = time.time() - start_time
|
|
|
|
|
remaining_timeout = max(0, timeout - elapsed)
|
|
|
|
|
|
|
|
|
|
if not self.wait_for_thread(thread_info.thread_id, timeout=remaining_timeout):
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def get_active_thread_count(self) -> int:
|
|
|
|
|
with self._lock:
|
|
|
|
|
return sum(1 for info in self._threads.values() if info.is_alive())
|
|
|
|
|
|
|
|
|
|
def get_all_threads_info(self) -> Dict[str, ThreadInfo]:
|
|
|
|
|
with self._lock:
|
|
|
|
|
return dict(self._threads)
|
|
|
|
|
|
|
|
|
|
def get_thread_count(self) -> int:
|
|
|
|
|
with self._lock:
|
|
|
|
|
return len(self._threads)
|
|
|
|
|
|
|
|
|
|
def set_exception_handler(self, handler: Callable[[str, Exception], None]) -> None:
|
|
|
|
|
self._exception_handler = handler
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thread_module_manager = ThreadModuleManager()
|