跳到主要内容

1 篇博文 含有标签「concurrent task」

查看所有标签

· 阅读需 4 分钟

并发任务控制题

  • 实现 SuperTask 类
// 辅助函数,指定时间后 Promise 完成
function timeout(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, time)
})
}

const superTask = new SuperTask()

function addTask(time, name) {
superTask
.add(() => timeout(time))
.then(() => {
console.log(`任务 ${name} 完成`)
})
}
  • 要求
addTask(10000, 1) // 10000ms 后输出:任务1完成
addTask(5000, 2) // 5000ms 后输出:任务2完成
addTask(3000, 3) // 8000ms 后输出:任务3完成
addTask(4000, 4) // 12000ms 后输出:任务4完成
addTask(5000, 5) // 15000ms 后输出:任务5完成
  • 分析

SuperTask 实例具有 add 方法,接收一个任务(同步/异步),返回一个 Promise,当 Promise 完成后打印完成日志

任务1和任务2都是在指定时间后输出的,但任务3是在任务2后面输出的,由此推断 SuperTask 默认最大并发数为 2

类比于银行柜台排队办事,一共有两个柜台,最多同时处理两位客户的任务。后续的客户需要排队等待,哪个柜台完成了就去补位执行

  • 实现

需要维护三个属性,分别是最大并发任务数(默认2)、正在执行的任务数以及任务队列

add 方法接受一个新任务,返回一个 Promise,在这个 Promise 中,将新任务加入到任务队列

类比于我们去银行排队办事的「叫号」行为,在每次添加任务后,就需要去触发叫号过程,尝试执行任务

将尝试执行任务的逻辑抽离为私有方法 _run,当当前执行任务数量小于最大并发数量、任务队列存在任务的情况下,循环执行任务

  • 为啥用 while

因为我们不仅要检查当前正在执行的任务数是否小于并行任务数,如果是的话就启动一个新的任务,而且我们还要在每次任务完成时再次进行这个检查。如果还有等待执行的任务并且当前正在执行的任务数仍小于并行任务数,那么我们还需要启动更多的任务。while 循环在当前 context 中直到满足该条件才会停止,这正是我们使用 while 的原因。

  • 要使用 Promise.resolve 包裹 task 返回值

使用 Promise.resolve() 可以确保不论那个 task 的返回值是否为 Promise 对象 ,我们总是在处理一个 Promise 对象。这就确保了我们可以在任务完成或失败后调用 .then.catch 方法。

执行完毕后,无论成功失败,都会(finally)再次调用 _run 方法检查是否还有任务可以执行

class SuperTask {
constructor(parallelCount = 2) {
this.parallelCount = parallelCount
this.tasks = []
this.runningCount = 0
}

add(task) {
return new Promise((resolve, reject) => {
this.tasks.push({
task,
resolve,
reject
})
this._run()
})
}

_run() {
while (
this.runningCount < this.parallelCount &&
this.tasks.length
) {
this.runningCount++
const { task, resolve, reject } = this.tasks.shift()
Promise.resolve(task())
.then(resolve, reject)
.finally(() => {
this.runningCount--
this._run()
})
}
}
}