我在 Swift 5.5 和 iOS 15 中有以下代码
func getReviewIds() {
var reviewIds: [Int] = []
Task {
let ids = await getReviewIdsFromGoogle()
reviewIds.append(contentsOf: ids)
}
print("outside")
}
func getReviewIdsFromGoogle() async -> [Int] {
await withUnsafeContinuation { continuation in
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
continuation.resume(returning: [1,2,3])
}
}
}
我在以下行的
getReviewIdsFromGoogle
函数中收到错误:
reviewIds.append(contentsOf: ids)
Mutation of captured var 'reviewIds' in concurrently-executing code
我知道我可以将
getReviewIdsFromGoogle
设为 async
函数,而不是使用 async
闭包,但是如何使用闭包解决这个问题。
为了防止数据竞争,您必须使用并发操作对变量的同步访问,并且编译器不允许您直接更改数组。为了避免这个问题,您可以使用
actor
实例实现对数据的隔离访问,例如:
actor Store {
var reviewIds: [Int] = []
func append(ids: [Int]) {
reviewIds.append(contentsOf: ids)
}
}
func getReviewIds() {
let store = Store()
Task {
let ids = await getReviewIdsFromGoogle()
await store.append(ids: ids)
print(await store.reviewIds)
}
}
一旦启动异步上下文(例如创建新的
Task
),您就无法将数据传递回原始同步上下文,因为这将要求原始上下文在等待异步结果时“阻塞”。
Swift 不允许在其并发模型中阻塞,因为这可能会导致线程死锁。
每个线程都需要能够“前进”。
您只需使用
Task
上下文的结果调用另一个函数来处理返回的值。
这个过程是否是另一个 async
功能取决于您,具体取决于您需要做什么。
func getReviewIDs() {
Task {
let result = await getReviewIdsFromGoogle()
process(ids: result)
}
}
func process(ids: [Int]) {
print("now process ids: \(ids)")
}
func getReviewIdsFromGoogle() async -> [Int] {
await withUnsafeContinuation { continuation in
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
continuation.resume(returning: [1,2,3])
}
}
}
我在使用 GRDB 编写一些异步代码时遇到了这种情况,因为我的类型在保存时会使用 ID 进行自我更新:
struct MyType: MutablePersistableRecord {
var id: Int64?
// ...
// Update auto-incremented id upon successful insertion
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
在这种情况下,我并不真正关心在异步写入后访问更新的 ID,所以我只是复制实体:)。 显然这是低效的,因为它实际上复制了实例,但在我的情况下这种影响可以忽略不计,而且我不需要编写
actor
:
func perform(db: DatabaseQueue) async throws {
var item = MyType()
// item.foo = "bar"
let dupe1 = item
try! await db.write { db in
var dupe2 = dupe1
dupe2.save(db)
}
}
完成!