如何在循环中限制异步任务并发数,实现固定额度执行直至无待执行任务
固定并发量的异步任务执行控制实现
现有代码的问题
你的循环逻辑会在同步执行阶段把700个setTimeout全部注册到事件循环中,这些定时器到点后会直接触发对应回调,完全没有并发数拦截能力。只要时间到了,不管当前已经在运行多少个任务,新的回调都会立刻执行,根本无法达到“始终维持30个并行任务”的要求。
实现思路
要实现固定并发的效果,核心需要通过3个状态变量追踪任务执行进度,搭配“任务完成即补位”的逻辑形成滑动窗口:
- 记录总任务数、下一个待启动任务的索引、当前正在运行的任务数量
- 程序启动时先批量拉起30个任务,让运行中的任务数达到并发上限
- 给每个启动的任务绑定完成回调:只要任意一个任务执行完毕,就把运行计数减1,立刻从待启动队列里拿一个新任务补上,始终把运行中的任务数保持在30的水平
- 直到待启动队列空了、运行中的任务数也为0时,代表所有任务全部执行完毕
可直接运行的示例代码
首先需要把原来的异步任务改造为可追踪完成状态的Promise结构,不然无法监听任务结束事件:
function JobdeskAsync() { // 封装为Promise,返回可感知完成状态的对象 return new Promise((resolve) => { const delay = finishedTime(5000, 1000); setTimeout(() => { console.log(`任务完成,输出随机值:${Math.floor(Math.random() * 1000)}`); resolve(); }, delay); }); } function finishedTime(max, min) { return Math.floor(Math.random() * (max - min)) + min; } // 核心控制参数 const TOTAL_TASK_COUNT = 700; // 总任务数 const CONCURRENCY_LIMIT = 30; // 固定并行任务数 let pendingTaskIndex = 0; // 下一个待启动任务的索引 let runningTaskCount = 0; // 当前正在运行的任务数 // 任务启动逻辑:拉取下一个待执行任务 async function startNextTask() { // 没有剩余待启动任务时直接返回 if (pendingTaskIndex >= TOTAL_TASK_COUNT) return; const taskIndex = pendingTaskIndex; pendingTaskIndex++; runningTaskCount++; console.log(`启动第${taskIndex + 1}个任务,当前运行中任务数:${runningTaskCount}`); try { // 等待当前任务执行完成 await JobdeskAsync(); } finally { // 不管任务成功/失败,都要更新运行计数 runningTaskCount--; console.log(`第${taskIndex + 1}个任务结束,当前运行中任务数:${runningTaskCount},剩余待启动任务:${TOTAL_TASK_COUNT - pendingTaskIndex}`); // 立刻补位,拉取下一个任务维持并发数 startNextTask(); } } // 全量任务执行入口 async function runAll() { // 初始拉起30个任务达到并发上限 for (let i = 0; i < CONCURRENCY_LIMIT; i++) { startNextTask(); } // 阻塞等待所有任务全部执行完毕 while (runningTaskCount > 0 || pendingTaskIndex < TOTAL_TASK_COUNT) { await new Promise(resolve => setTimeout(resolve, 100)); } console.log(`全部${TOTAL_TASK_COUNT}个任务执行完成`); } // 启动执行 runAll();
关键注意点
- 必须把异步任务封装为Promise:只有拿到可追踪的完成事件,才能触发“补位”逻辑,原来的无返回值setTimeout写法无法监听任务结束时机
- 这种实现方式是典型的滑动窗口并发模型:不需要提前把所有700个任务的Promise全部存入数组,边执行边补位的方式在任务量极大时也不会产生额外内存压力
- 异常场景下的兜底逻辑已经放在
finally块中,哪怕某个任务执行报错,也不会卡住并发计数,导致后续任务无法正常补位
内容的提问来源于stack exchange,提问作者david stephen
相关产品推荐
相关产品推荐

