You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift Actor方法能否被并发交互?WWDC示例引发的困惑

关于Swift Actor并发调用的疑问

我正在观看WWDC2021的《用Swift Actor保护可变状态》视频,里面有个示例展示了Actor方法被并发调用的问题:

假设两个并发任务同时尝试获取同一张图片。第一个任务发现缓存中没有对应条目,便开始从服务器下载图片,随后因下载耗时较长被挂起。在第一个任务下载期间,服务器上该URL对应的图片可能已经更新。此时第二个并发任务尝试获取该图片,由于第一个任务的下载尚未完成,缓存里还是没有条目,于是也启动了下载,同样在下载过程中被挂起。一段时间后,第一个任务下载完成,回到Actor中填充缓存并返回一张猫的图片;接着第二个任务下载完成,它会用自己获取到的悲伤猫图片覆盖缓存里的同一条目。结果就是,同一个URL对应的图片被替换了,即便缓存已经被填充过。

多任务并发下载导致缓存覆盖的流程示意图

Actor的核心设计不就是保证同一时间只有一个调用者能直接和它交互吗?

我写了一段示例代码,运行后发现“BEGIN increment”总是紧跟“END increment”,后续对increment方法的调用必须等待前一个完成:

actor Counter {
    var count = 1

    func increment() {
        print("BEGIN increment")
        let url = URL(string: "https://google.com")!
        let data = try! Data(contentsOf: url)
        let string = String(data: data, encoding: .utf8) ?? ""
        print("END increment")
        count += 1
    }
}


struct ContentView: View {
    @State var counter = Counter()

    var body: some View {
        Button {
            Task.detached {
                await counter.increment()
            }
        } label: {
            Text("Click")
        }
    }
}

问题解析

你遇到的差异核心在于同步阻塞操作和异步挂起操作的区别:

  • 你的代码里,Data(contentsOf:)是同步阻塞调用,它会一直占用Actor的执行线程,直到数据加载完成。在这个过程中,Actor的执行权没有被释放,后续任务必须排队等待当前方法完全执行完毕才能进入Actor,所以你看到的是“BEGIN”和“END”连续出现,没有并发情况。

  • 而WWDC视频里的下载逻辑是异步的(比如使用URLSession的异步API),当Actor方法执行到await下载任务时,当前任务会挂起并释放Actor的执行权。这时候,等待中的第二个任务就可以进入Actor执行方法的前半部分——检查缓存。由于第一个任务还在下载,缓存还没被填充,第二个任务就会发起自己的下载请求。

Actor的串行执行规则是:在Actor的执行上下文中,代码是串行执行的,但遇到await时会让出执行权。这就导致了视频里的场景:两个任务都能进入Actor的方法执行缓存检查逻辑,因为第一个任务在异步下载时让出了Actor的使用权。

要模拟视频里的场景,你需要把同步的Data(contentsOf:)改成异步的网络请求,比如:

actor ImageCache {
    private var cache: [URL: Data] = [:]

    func fetchImage(url: URL) async throws -> Data {
        if let cachedData = cache[url] {
            return cachedData
        }

        // 异步网络请求,会触发挂起并释放Actor执行权
        let (data, _) = try await URLSession.shared.data(from: url)
        cache[url] = data
        return data
    }
}

当两个任务同时调用fetchImage时,第一个任务执行到await URLSession.shared.data(from:)就会挂起,Actor的执行权被释放,第二个任务进入方法检查缓存——此时缓存还是空的,所以它也会发起异步下载。等两个下载任务先后完成后,就会出现第二个任务覆盖缓存的情况。

内容的提问来源于stack exchange,提问作者onmyway133

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 10:15:25