同时 运行 个带有未命名 async let 的异步任务

Concurrently run async tasks with unnamed async let

使用 Swift 并发,是否有可能拥有几乎类似于 'unnamed' async let 的东西?

这是一个例子。您有以下演员:

actor MyActor {
    private var foo: Int = 0
    private var bar: Int = 0

    func setFoo(to value: Int) async {
        foo = value
    }

    func setBar(to value: Int) async {
        bar = value
    }

    func printResult() {
        print("foo =", foo)
        print("bar =", bar)
    }
}

现在我想使用给定的方法设置 foobar。简单用法如下所示:

let myActor = MyActor()
await myActor.setFoo(to: 5)
await myActor.setBar(to: 10)
await myActor.printResult()

然而这段代码是顺序的运行。出于所有意图和目的,假设 setFoo(to:)setBar(to:) 可能是一项长期 运行ning 任务。您还可以假设这些方法是互斥的(不共享变量并且不会相互影响)。

要使此代码成为最新代码,可以使用 async let。但是,这只会启动任务,直到它们稍后被 awaited。在我的示例中,您会注意到我不需要这些方法中的 return 值。我只需要在 printResult() 被调用之前,之前的任务已经完成。

我可以想出以下几点:

let myActor = MyActor()
async let tempFoo: Void = myActor.setFoo(to: 5)
async let tempBar: Void = myActor.setBar(to: 10)
let _ = await [tempFoo, tempBar]
await myActor.printResult()

明确创建这些任务,然后 awaiting 一个数组似乎是不正确的。这真的是最好的方法吗?

这可以通过使用 withTaskGroup(of:returning:body:) 的任务组来实现。方法调用是单独的任务,然后我们 await waitForAll() 在所有任务完成后继续。

代码:

await withTaskGroup(of: Void.self) { group in
    let myActor = MyActor()

    group.addTask {
        await myActor.setFoo(to: 5)
    }
    group.addTask {
        await myActor.setBar(to: 10)
    }

    await group.waitForAll()
    await myActor.printResult()
}

我将你的 actor 设为 class 以允许同时执行这两种方法。

import Foundation

final class Jeep {
    private var foo: Int = 0
    private var bar: Int = 0

    func setFoo(to value: Int) {
        print("begin foo")
        foo = value
        sleep(1)
        print("end foo \(value)")
    }

    func setBar(to value: Int) {
        print("begin bar")
        bar = value
        sleep(2)
        print("end bar \(bar)")
    }

    func printResult() {
        print("printResult foo:\(foo), bar:\(bar)")
    }
}

let jeep = Jeep()
let blocks = [ 
    { jeep.setFoo(to: 1) }, 
    { jeep.setBar(to: 2) },
]

// ...WORK

RunLoop.current.run(mode: RunLoop.Mode.default, before: NSDate(timeIntervalSinceNow: 5) as Date)

将 WORK 替换为以下之一:

// no concurrency, ordered execution
for block in blocks {
    block() 
}
jeep.printResult()


// concurrency, unordered execution, tasks created upfront programmatically
Task {
    async let foo: Void = blocks[0]()
    async let bar: Void = blocks[1]()
    await [foo, bar]
    jeep.printResult()
}

// concurrency, unordered execution, tasks created upfront, but started by the system (I think)
Task {
    await withTaskGroup(of: Void.self) { group in
        for block in blocks {
            group.addTask { block() }
        } 
    }
    // when the initialization closure exits, all child tasks are awaited implicitly
    jeep.printResult()
}

// concurrency, unordered execution, awaited in order
Task {
    let tasks = blocks.map { block in
        Task { block() } 
    }
    for task in tasks {
        await task.value
    }
    jeep.printResult()
}

// tasks created upfront, all tasks start concurrently, produce result as soon as they finish
let stream = AsyncStream<Void> { continuation in
    Task {
        let tasks = blocks.map { block in
            Task { block() }
        }
        for task in tasks {
            continuation.yield(await task.value)
        }
        continuation.finish()
    }
}
Task {
    // now waiting for all values, bad use of a stream, I know
    for await value in stream { 
        print(value as Any)
    }
    jeep.printResult()
}