它有助于在某些情况下处理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:如果您测试这种方法,您可能会注意到,针对同一(非主)参与者运行的多个任务可能仍然在多个线程上运行,或者可能在不同的线程上继续运行.什么给予?
Task
和Actor
是SWIFT并发中的high-level个工具,作为开发人员,它们是您最常接触的工具,但让我们来了解一下实现细节:
Task
实际上是not,是SWIFT并发中工作的低级原语;Job
是.Job
代表Task
中await
条语句之间的代码,您永远不会自己编写Job
;SWIFT编译器提取Task
并从中创建Job
Job
本身不是由Actor
运行的,而是由Executor
运行的,而且,您永远不会自己直接实例化或使用Executor
.但是,实际运行提交给该参与者的作业(job)的每个Actor
has an Executor
associated with it
这才是日程安排真正发挥作用的地方.目前,SWIFT并发中有两个主要的执行者:
- 一个合作社,global executor,在cooperative thread pool上调度作业(job),以及
- main执行程序,专门在主线程上调度作业(job)
所有非MainActor
参与者当前都使用全局执行器来调度和执行作业(job),MainActor
使用主执行器来执行相同的操作.
作为SWIFT并发性的user%,这意味着:
- 如果您将一段代码指定为在主线程上独占运行,则可以在
MainActor
上调度它,并且可以保证它在该线程上运行only
- 如果您在任何其他
Actor
上创建任务,它将在全局协作线程池中的一个(或多个)线程上运行
- 如果您在specific
Actor
上运行,则Actor
将为您管理锁和其他并发原语,以便任务不会同时修改共享状态
有了所有这些,让我们来回答你的问题:
为什么SWIFT不能保证在相同的帖子上恢复?
正如上述 comments 所述--因为:
- 这不应该是必要的(因为任务应该以一种方式隔离,即"我们在哪个线程上?"无关紧要)和
- 能够使用任何一个可用的协作线程意味着您可以更快地继续在所有工作上取得进展
然而,"主线程"is在许多方面都是特殊的,因此,@MainActor
必然只使用该线程.当您确实需要确保自己只在主线程上时,您可以使用主参与者.
有没有什么规则可以用来确定恢复线程?
对于非@MainActor
注释的任务,唯一的规则是:协作线程池中的第一个可用线程将承担工作.
改变这一行为需要编写和使用您自己的Executor
,这还不太可能(though there are some plans on making this possible).
有没有办法影响这种行为,例如,确保它在主线程上恢复?
对于arbitrary个线程,不需要--您需要提供自己的执行器来控制低级别的细节.
但是,对于main线程,您有几个工具:
当您使用Task.init(priority:operation:)
创建Task
时,它默认继承current参与者,无论这是什么参与者.这意味着,如果您已经在主执行元上运行,则任务将继续使用当前执行元;但如果您是aren't,则不会.要显式批注希望任务在主执行元上运行,可以显式批注其操作:
Task { @MainActor in
// ...
}
这将确保无论在哪个参与者上创建Task
,所包含的代码都将仅在主参与者上运行.
从within到Task
:不管你现在是哪个演员,你总是可以直接把工作提交到MainActor.run(resultType:body:)
分的主要演员身上.body
闭包已经被注释为@MainActor
,并将保证在主线程上执行
请注意,创建detached task将从当前参与者继承,从而确保分离的任务将通过全局执行器隐式调度.
我对Swift并发性的研究发现,从主线程(在SwiftUI中)上运行的代码开始的任务正在另一个线程上执行它的块,从而触发了上面的后续问题.
在这里查看特定的代码来解释到底发生了什么会很有帮助,但有两种可能性:
- 您创建了一个非显式的带
@MainActor
注释的Task
,它恰好在当前线程上开始执行.然而,因为你不是主演的bound岁,所以它碰巧被一个合作线程挂起和恢复
- 您创建了一个包含其他
Task
的Task
,这些Task
可能在其他参与者上运行,或者是显式分离的任务-该工作在另一个线程上继续
欲了解更多细节,请查看WWDC2021的Swift concurrency: Behind the scenes个,@Rob在 comments 中链接了该链接.还有更多关于正在发生的事情的细节,了解更低层次的视图可能会很有趣.