如何在 Swift 中捕获异步闭包内的局部变量?

问题描述 投票:0回答:3

我在 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
闭包,但是如何使用闭包解决这个问题。

swift async-await
3个回答
20
投票

为了防止数据竞争,您必须使用并发操作对变量的同步访问,并且编译器不允许您直接更改数组。为了避免这个问题,您可以使用

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)
    }
}

11
投票

一旦启动异步上下文(例如创建新的

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])
        }
    }
}

0
投票

我在使用 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)
    }
}

完成!

© www.soinside.com 2019 - 2024. All rights reserved.