async_tasks.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import asyncio
  2. from typing import Callable, Coroutine, List, Any, Dict
  3. async def run_tasks_with_async_worker_group(
  4. task_list: List[Any],
  5. handler: Callable[[Any], Coroutine[Any, Any, None]],
  6. *,
  7. description: str = None,
  8. unit: str = "item",
  9. max_concurrency: int = 20,
  10. fail_fast: bool = False,
  11. show_progress: bool = True,
  12. ) -> Dict[str, Any]:
  13. """使用异步 worker pool 处理 I/O 密集型任务
  14. Args:
  15. task_list: 任务对象列表
  16. handler: 异步处理函数,接受单个任务对象
  17. description: 进度条描述
  18. unit: 进度条单位
  19. max_concurrency: 最大并发 worker 数
  20. fail_fast: True 时任一 worker 异常立即中止全部
  21. show_progress: 是否显示 tqdm 进度条(需要安装 tqdm)
  22. Returns:
  23. {"total_task": int, "processed_task": int, "errors": [(idx, obj, exc), ...]}
  24. """
  25. if not task_list:
  26. return {"total_task": 0, "processed_task": 0, "errors": []}
  27. total_task = len(task_list)
  28. processed_task = 0
  29. errors: List[tuple] = []
  30. queue: asyncio.Queue = asyncio.Queue()
  31. cancel_event = asyncio.Event()
  32. counter_lock = asyncio.Lock()
  33. # 进度条(可选)
  34. if show_progress:
  35. try:
  36. from tqdm.asyncio import tqdm as async_tqdm
  37. pbar = async_tqdm(total=total_task, unit=unit, desc=description)
  38. except ImportError:
  39. pbar = None
  40. else:
  41. pbar = None
  42. async def worker(worker_id: int):
  43. nonlocal processed_task
  44. while True:
  45. item = await queue.get()
  46. if item is None:
  47. queue.task_done()
  48. break
  49. idx, task_obj = item
  50. try:
  51. if cancel_event.is_set():
  52. return
  53. await handler(task_obj)
  54. async with counter_lock:
  55. processed_task += 1
  56. except Exception as e:
  57. if fail_fast:
  58. cancel_event.set()
  59. raise
  60. errors.append((idx, task_obj, e))
  61. finally:
  62. if pbar:
  63. pbar.update()
  64. queue.task_done()
  65. workers = [asyncio.create_task(worker(i)) for i in range(max_concurrency)]
  66. try:
  67. for index, task in enumerate(task_list, start=1):
  68. await queue.put((index, task))
  69. await queue.join()
  70. except Exception:
  71. for w in workers:
  72. w.cancel()
  73. raise
  74. finally:
  75. for _ in range(max_concurrency):
  76. await queue.put(None)
  77. await asyncio.gather(*workers, return_exceptions=True)
  78. if pbar:
  79. pbar.close()
  80. return {
  81. "total_task": total_task,
  82. "processed_task": processed_task,
  83. "errors": errors,
  84. }
  85. async def run_tasks_with_asyncio_task_group(
  86. task_list: List[Any],
  87. handler: Callable[[Any], Coroutine[Any, Any, None]],
  88. *,
  89. description: str = None,
  90. unit: str = "item",
  91. max_concurrency: int = 20,
  92. fail_fast: bool = False,
  93. show_progress: bool = True,
  94. ) -> Dict[str, Any]:
  95. """使用 asyncio.TaskGroup 处理 I/O 密集型任务
  96. Args:
  97. task_list: 任务对象列表
  98. handler: 异步处理函数,接受单个任务对象
  99. description: 进度条描述
  100. unit: 进度条单位
  101. max_concurrency: 最大并发数(通过 Semaphore 控制)
  102. fail_fast: True 时任一任务异常立即中止全部
  103. show_progress: 是否显示 tqdm 进度条
  104. Returns:
  105. {"total_task": int, "processed_task": int, "errors": [(idx, obj, exc), ...]}
  106. """
  107. if not task_list:
  108. return {"total_task": 0, "processed_task": 0, "errors": []}
  109. processed_task = 0
  110. total_task = len(task_list)
  111. errors: List[tuple] = []
  112. semaphore = asyncio.Semaphore(max_concurrency)
  113. # 进度条(可选)
  114. if show_progress:
  115. try:
  116. from tqdm.asyncio import tqdm as async_tqdm
  117. pbar = async_tqdm(total=total_task, unit=unit, desc=description)
  118. except ImportError:
  119. pbar = None
  120. else:
  121. pbar = None
  122. lock = asyncio.Lock()
  123. async def _run_single_task(task_obj: Any, idx: int):
  124. nonlocal processed_task
  125. async with semaphore:
  126. try:
  127. await handler(task_obj)
  128. async with lock:
  129. processed_task += 1
  130. except Exception as e:
  131. if fail_fast:
  132. raise e
  133. errors.append((idx, task_obj, e))
  134. finally:
  135. if pbar:
  136. pbar.update()
  137. async with asyncio.TaskGroup() as task_group:
  138. for index, task in enumerate(task_list, start=1):
  139. task_group.create_task(
  140. _run_single_task(task, index),
  141. name=f"processing {description}-{index}",
  142. )
  143. if pbar:
  144. pbar.close()
  145. return {
  146. "total_task": total_task,
  147. "processed_task": processed_task,
  148. "errors": errors,
  149. }