You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

循环调用带完成回调的函数时如何保证结果顺序

循环调用带完成回调的函数时如何保证结果顺序

嘿,这个问题我之前做异步任务的时候也踩过坑!你碰到的核心问题就是异步回调的执行顺序不固定,但我们需要把结果对应到原来的调用顺序上对吧?其实你的实验已经摸到门道了——闭包会捕获当前作用域里的值类型变量的当前值,而不是引用,这正是我们解决问题的关键!

最优方案:给每个异步调用标记顺序索引

我们可以在循环的时候记录每个调用的位置(索引),把这个索引和回调绑定,这样不管回调什么时候回来,都能把结果放到数组的正确位置上,而且还能保留并行调用的效率。

修改你的retrieveAllWords方法就行:

func retrieveAllWords() {
    // 先初始化数组,预留好所有位置,避免append导致的顺序混乱
    words = Array(repeating: "", count: 21) // 0...20一共21个元素
    for index in 0...20 {
        findNextWord { [weak self] word in
            guard let self = self else { return }
            // 直接把结果放到对应索引的位置,不管回调顺序如何
            self.words[index] = word
        }
    }
}

为啥这个能行?就像你实验里的a_not_self一样,循环里的index是每次迭代的局部变量,闭包会捕获它当时的具体值——第一个循环捕获的是0,第二个是1,以此类推。所以每个回调回来时,都知道自己该放到数组的哪个位置,完美解决顺序问题!

备选方案:串行执行异步调用(适合必须顺序执行的场景)

如果你的业务逻辑要求必须等前一个findNextWord完成后再调用下一个,那可以用串行队列+DispatchGroup来强制顺序执行,不过这样会丢失并行调用的效率:

func retrieveAllWords() {
    let serialQueue = DispatchQueue(label: "com.example.wordSerialQueue")
    serialQueue.async { [weak self] in
        guard let self = self else { return }
        for _ in 0...20 {
            let group = DispatchGroup()
            group.enter()
            self.findNextWord { word in
                self.words.append(word)
                group.leave()
            }
            group.wait() // 等待当前异步调用完成,再进行下一次循环
        }
    }
}

这种方法的好处是完全不用管顺序,因为调用本身就是串行的,但代价是所有异步任务变成了串行执行,速度会慢很多,所以只在必须依赖前一个结果的场景下用。

再聊下你的实验发现

你实验里的a_not_selfself?.a的差异,就是闭包的值捕获特性:a_not_self是值类型(Int),闭包捕获的是它当时的副本;而self?.a是引用类型的属性,闭包捕获的是self的引用,所以取到的是最新值。这也是为什么我们能用索引法的核心原因——每个闭包里的索引都是当时循环的那个固定值。

备注:内容来源于stack exchange,提问作者sudocu

火山引擎 最新活动