"The Swift Programming Language"的并发性章节的介绍部分,我读到:

当一个异步函数恢复时,SWIFT不会创建任何 保证该函数将在哪个线程上运行.

这让我很惊讶.这似乎很奇怪,例如,与在p线程中等待信号量相比,执行可以跳过线程.

这就引出了以下问题:

  • 为什么SWIFT不能保证在相同的帖子上恢复?

  • 有没有什么规则可以让恢复的线程 有决心吗?

  • 有没有办法影响这种行为,例如,确保它在主线程上恢复?

编辑:我对SWIFT并发性的研究发现,从main thread(在SwiftUI中)上运行的代码开始的Taskanother thread上执行它的块,从而引发了上面的后续问题.

推荐答案

它有助于在某些情况下处理SWIFT并发性:SWIFT并发性try 提供higher-level方法来处理并发代码,与您可能已经习惯的线程模型以及线程、并发原语(锁定、信号量)等的低级管理有所不同,因此您不必花费任何时间考虑低级管理.

从TSPL的Actors section开始,从您的报价页面往下一点:

您可以使用任务将程序分解为多个独立的并发片段.任务彼此隔离,这使得它们可以安全地同时运行…

在SWIFT并发中,Task代表可以并发完成的isolated位工作,这里isolation的概念非常重要:当代码与周围的上下文隔离时,它可以在不影响外部世界或受其影响的情况下完成所需的工作.这意味着在理想情况下,真正隔离的任务可以在任何时间在任何线程上运行,并且可以根据需要跨线程交换,而不会对正在完成的工作(或程序的其余部分)产生任何可测量的影响.

正如@Alexander在上面的 comments 中提到的,如果处理得当,这是一个巨大的好处:当以这种方式隔离工作时,任何可用的线程都可以 Select 并执行该工作,使您的进程有机会完成更多的工作,而不是等待特定的线程可用.

However:并不是所有的代码都可以完全隔离到以这种方式运行;在某些情况下,some代码需要与外部世界交互.在某些情况下,任务需要相互连接才能共同完成工作;在其他情况下,如UI工作,任务需要与非并发代码协调才能达到该效果.Actors是SWIFT并发提供的帮助实现这种协调的工具.

Actor有助于确保任务在特定的context中运行,相对于也需要在该上下文中运行的其他任务是连续的.继续上面的引述:

…这使得它们同时运行是安全的,但有时您需要在任务之间共享一些信息.参与者允许您在并发代码之间安全地共享信息.

…执行元一次只允许一个任务访问其可变状态,这使得多个任务中的代码与执行元的同一实例交互是安全的.

除了使用Actor作为孤立的国家避风港之外,正如该部分其余部分所示,您还可以创建Task,并使用它们应该在其上下文中运行的Actor来显式地注释它们的身体.例如,要使用TSPL中的TemperatureLogger示例,您可以在TemperatureLogger的上下文中运行任务,如下所示:

Task { @TemperatureLogger in
    // This task is now isolated from all other tasks which run against
    // TemperatureLogger. It is guaranteed to run _only_ within the
    // context of TemperatureLogger.
}

同样的道理也适用于与MainActor人竞争:

Task { @MainActor in
    // This code is isolated to the main actor now, and won't run concurrently
    // with any other @MainActor code.
}

这种方法适用于可能需要访问共享状态并且需要彼此隔离的任务,but:如果您测试这种方法,您可能会注意到,针对同一(非主)参与者运行的多个任务可能仍然在多个线程上运行,或者可能在不同的线程上继续运行.什么给予?


TaskActor是SWIFT并发中的high-level个工具,作为开发人员,它们是您最常接触的工具,但让我们来了解一下实现细节:

  1. Task实际上是not,是SWIFT并发中工作的低级原语;Job是.Job代表Taskawait条语句之间的代码,您永远不会自己编写Job;SWIFT编译器提取Task并从中创建Job
  2. Job本身不是由Actor运行的,而是由Executor运行的,而且,您永远不会自己直接实例化或使用Executor.但是,实际运行提交给该参与者的作业(job)的每个Actor has an Executor associated with it

这才是日程安排真正发挥作用的地方.目前,SWIFT并发中有两个主要的执行者:

  1. 一个合作社,global executor,在cooperative thread pool上调度作业(job),以及
  2. main执行程序,专门在主线程上调度作业(job)

所有非MainActor参与者当前都使用全局执行器来调度和执行作业(job),MainActor使用主执行器来执行相同的操作.

作为SWIFT并发性的user%,这意味着:

  1. 如果您将一段代码指定为在主线程上独占运行,则可以在MainActor上调度它,并且可以保证它在该线程上运行only
  2. 如果您在任何其他Actor上创建任务,它将在全局协作线程池中的一个(或多个)线程上运行
    • 如果您在specific Actor上运行,则Actor将为您管理锁和其他并发原语,以便任务不会同时修改共享状态

有了所有这些,让我们来回答你的问题:

为什么SWIFT不能保证在相同的帖子上恢复?

正如上述 comments 所述--因为:

  1. 这不应该是必要的(因为任务应该以一种方式隔离,即"我们在哪个线程上?"无关紧要)和
  2. 能够使用任何一个可用的协作线程意味着您可以更快地继续在所有工作上取得进展

然而,"主线程"is在许多方面都是特殊的,因此,@MainActor必然只使用该线程.当您确实需要确保自己只在主线程上时,您可以使用主参与者.

有没有什么规则可以用来确定恢复线程?

对于非@MainActor注释的任务,唯一的规则是:协作线程池中的第一个可用线程将承担工作.

改变这一行为需要编写和使用您自己的Executor,这还不太可能(though there are some plans on making this possible).

有没有办法影响这种行为,例如,确保它在主线程上恢复?

对于arbitrary个线程,不需要--您需要提供自己的执行器来控制低级别的细节.

但是,对于main线程,您有几个工具:

  1. 当您使用Task.init(priority:operation:)创建Task时,它默认继承current参与者,无论这是什么参与者.这意味着,如果您已经在主执行元上运行,则任务将继续使用当前执行元;但如果您是aren't,则不会.要显式批注希望任务在主执行元上运行,可以显式批注其操作:

    Task { @MainActor in
        // ...
    }
    

    这将确保无论在哪个参与者上创建Task,所包含的代码都将仅在主参与者上运行.

  2. withinTask:不管你现在是哪个演员,你总是可以直接把工作提交到MainActor.run(resultType:body:)分的主要演员身上.body闭包已经被注释为@MainActor,并将保证在主线程上执行

请注意,创建detached task将从当前参与者继承,从而确保分离的任务将通过全局执行器隐式调度.

我对Swift并发性的研究发现,从主线程(在SwiftUI中)上运行的代码开始的任务正在另一个线程上执行它的块,从而触发了上面的后续问题.

在这里查看特定的代码来解释到底发生了什么会很有帮助,但有两种可能性:

  1. 您创建了一个非显式的带@MainActor注释的Task,它恰好在当前线程上开始执行.然而,因为你不是主演的bound岁,所以它碰巧被一个合作线程挂起和恢复
  2. 您创建了一个包含其他TaskTask,这些Task可能在其他参与者上运行,或者是显式分离的任务-该工作在另一个线程上继续

欲了解更多细节,请查看WWDC2021的Swift concurrency: Behind the scenes个,@Rob在 comments 中链接了该链接.还有更多关于正在发生的事情的细节,了解更低层次的视图可能会很有趣.

Ios相关问答推荐

SwiftUI.从自定义视图修改器访问自定义视图子视图

如何在iPhone 15上消除.NET Maui中状态栏和页面之间的差距

如何从Windows PC在iPhone上安装Flutter 应用程序

执行devicectl:ProcessException时出错:进程异常退出:错误:命令超时超过5.0秒

NumberFormatter 无法使用 Xcode 15.0 beta 正确识别 iOS 17 beta 中的 Locale 货币设置

iOS 16.4 更新后 Xcode 14.3 构建错误:找不到 libarclite_iphoneos.a,链接器命令失败

SwiftUI - 如何从主题感知命名 colored颜色 中获取正确的 RGB 值?

Flutter firebase_auth 中的无效应用程序凭据/令牌不匹配

iOS 16 堆栈视图中按钮的奇怪动画

转换在 NavigationView 中不起作用

有没有办法访问自动生成的 Codable 一致性的编码键?

我可以在 Apple-Watch 上使用 iPhone 的摄像头扫描 SwiftUi 中的二维码(例如用于登录)吗

使用实时数据库的应用程序无法在被 Firebase 阻止的罗马尼亚 ISP 中运行

Got Unrecognized selector -replacementObjectForKeyedArchiver: 在 Swift 中实现 NSCoding 时崩溃

不变违规:应用程序 AwesomeProject 尚未注册使用静态 jsbundle 为 iOS 设备构建时

无法将NSTaggedPointerString类型的值转换为NSNumber

在 Xcode 5 中为超级视图添加间距约束

Swift tableView 分页

Xcode:此时无法安装此应用程序.

错误 itms-90035 - Xcode