我将Strict Concurrency Checking设置为Complete,并且在Xcode15.0.1和Xcode15.1测试版3中编译以下代码时没有任何警告.

在运行它时,它显示出并发问题.允许inc()方法同时在两个任务上运行.

我的问题:

  • 我认为这不应该在没有警告的情况下进行编译,这样做正确吗?
  • 这段代码的哪一部分是非法的?我想是run()年的Task米比赛.但是也许非MainActor inc()方法不应该从MainActor同步调用.
class Counter {
    private var counter = 0

    func inc() -> Int {
        let v = counter + 1
        for _ in 0...1000 {}
        counter = v
        return v
    }

    @MainActor
    func run() {
        Task {
            while true {
                try? await Task.sleep(for: .seconds(1))
                let v = inc()
                print("xxx t1", v)
            }
        }
    }
}

class Experiment {
    func start() {
        Task {
            let counter = Counter()
            await counter.run()

            while true {
                try? await Task.sleep(for: .seconds(1))
                let v = counter.inc()
                print("xxx t2", v)
            }
        }
    }
}

Update 2023-12-04:

我用当前的SWIFT 5.10快照测试了这段代码,现在第await counter.run()行触发警告"Passing argument of non-sendable type 'Counter' into main actor-isolated context may introduce data races".

看起来这个PR修复了它:https://github.com/apple/swift/pull/67730

推荐答案

你问:

  • 我是否正确地假设这不应该在没有警告的情况下编译?

我同意.我本以为会在编译时发出警告.此代码不是线程安全的.Counter不是Sendable,它是在@Sendable闭包内捕获的.

FWIW,我体验到的行为(在Xcode 15.0.1和15.1 Beta 3中)如下:

  1. 如果我删除了run方法上的@MainActor隔离,则在使用"严格并发判断"构建设置"Complete"时会收到相应的编译时错误:

    在"@Sendable"闭包中捕获不可发送类型为"count"的"self".

    "严格并发判断"生成设置控制您将收到的编译时警告.它主要是判断跨越任务边界的对象是否为Sendable.(对于那些不熟悉这些问题的人来说,WWDC 2022视频Eliminate data races using Swift Concurrency是一个很好的入门读物.)

    所以这个警告是正确的:Counter是不可发送的.此代码不是线程安全的.

  2. 但是,如果我恢复run@MainActor隔离,如您的原始示例所示,编译器将无法生成相关的警告(即使Counter不比上面更安全;它仍然是不可发送的).

    话虽如此,您还是报告了运行时错误.我只在打开线程消毒器(TSAN)时显示运行时错误.顺便说一句,这就是为什么"严格并发判断"的编译时警告通常比运行时判断好得多的原因:许多线程不安全的行为在运行时并不一致地表现出来."严格的并发判断"可以检测在运行时可能很难显现的线程安全错误.

    但回到您的问题上,不清楚为什么编译器在只有run方法的@MainActor隔离的情况下无法检测到Counter是不可发送的.与没有@MainActor隔离相比,仅隔离这一个函数并不会使Counter更具线程安全性.(为了保护SWIFT编译器的作者,所有这些可发送性判断代码都必须非常复杂.)

你接着问:

  • 这段代码的哪一部分是非法的?

不管run是否独立于主要参与者,问题是该代码根本不是线程安全的.Counter不是Sendable.它公开了改变非隔离属性的inc函数,这意味着当由run启动的任务正在执行时,另一个线程可以并行调用inc.(或者,多个线程可以同时调用inc,无论是否曾经调用过run.)这段代码不是线程安全的.

有许多方法可以修复此代码.我们想把Counter做成Sendable型.我们可以将整个Counter类型隔离为@MainActor(或任何全局参与者).或者我们可以用classactor号来代替.或者你可以添加你自己的同步并将其标记为@unchecked Sendable(但是线程安全的正确性的负担现在落在你的肩上,编译器将无法验证这一点).但是,Counter并不是线程安全的.

Swift相关问答推荐

RealityKit(VisionOS)中的PhysicBodyComponent和DragGesture

有没有一种方法可以迭代可编码的代码(例如,JSON解析中的每一项)?

如何在 Vapor 中制作可选的查询过滤器

使用UICollectionViewFlowLayout创建水平的UICollectionView,并同时将单元格对齐到左边和右边的能力

If 语句在 Swift 中有 2 个日期

如何将视图添加到多行文本视图的末尾?

Swift Random Float Fatal Error:在无限范围内没有均匀分布

NavigationStack 和 TabView - 工具栏和标题不显示

如何在填充上添加 if else 语句? SwiftUI

将数据附加到 Firebase 实时数据库后,NavigationLink 将我重定向到上一个视图

如何在 Swift 中返回 Task 中定义的变量

如何使用 Date.ParseStrategy 在 Swift 5.6 中将字符串解析为日期?

Swift 2.0 方法不能标记为@objc,因为参数的类型不能在 Objective-C 中表示

Swift 2 中的新 @convention(c):我该如何使用它?

协议扩展中的where self是什么

用 UIBezierPath 画一条线

UILabel 文本不换行

使用相机进行人脸检测

使用 ARAnchor 插入 node 和直接插入 node 有什么区别?

我可以让#selector 引用 Swift 中的闭包吗?