我目前正在阅读《Apple Platforms 上的现代并发》一书。
本书多次使用“并发上下文”这个术语,但没有实际解释这种上下文的上下文。
例如:“任务有一个带有闭包的初始化器,它本身就是并发上下文”。 或者:“如果没有并发上下文,会发生什么?在调用层次结构的某个位置,您会发现根本没有并发上下文来运行代码。”
有人可以用简单易懂的方式解释“并发上下文”这个术语吗?
在网上搜索并阅读提到的书。
tl;博士
对于 Swift 并发性,术语“上下文”是代码所在的执行环境。在 Swift 并发之外运行的代码被称为在“同步上下文”中运行。在 Swift 并发系统中运行的代码被认为是在“异步上下文”中运行。未隔离到同一参与者的两个单独的异步例程将被解释为位于单独的异步上下文中。
就其价值而言,术语“异步上下文”在SE-0296 – 异步/等待、SE-0304 – 结构化并发和SE-0306 – Actor中广泛使用(但从未正式定义) 。据我所知,苹果一般不使用“并发上下文”这个术语,而是使用“异步上下文”,但我们很容易猜测作者的意图。
要具体评论您与我们分享的引文,我们可能需要更丰富的摘录。但考虑一下你的第一个报价:
有一个带有闭包的初始值设定项,它本身就是并发上下文Task
Task {…}
将代表当前参与者(如果有)创建一个新的顶级任务。因此,如果 Task
是从同步上下文中创建的,是的,闭包将在不同的上下文(即异步上下文)上运行,但与当前参与者(如果有)隔离。但是,如果您在 Actor 隔离的异步上下文中使用 Task {…}
,则此新任务将被隔离到调用它的同一 Actor。因此,详细信息将根据创建 Task
的直接上下文而有所不同。
也许这是挑剔,但无论哪种方式,我都会犹豫地说闭包“是并发上下文”。闭包本身并不是一个“上下文”。更准确的说法是,将其描述为将在特定异步上下文上运行的闭包。
为了完整起见,我们应该注意,这种
Task {…}
行为与 Task.detached {…}
形成鲜明对比,后者将从不 代表当前参与者运行代码。
考虑第二个引文:
如果没有并发上下文会发生什么?在调用层次结构的某个位置,您会发现根本没有并发上下文来运行代码的情况。
我非常不清楚作者想说什么。在 Swift 并发中,它不会“在层次结构中向上”导航来确定它所处的上下文。(这就是它在 GCD 中的工作方式,这是 GCD 代码可能如此脆弱的原因之一;但这不是它的工作方式在 Swift 并发中。)它只是查看当前函数以查看哪个参与者(如果有)被隔离。如果当前函数与特定参与者隔离,则
Task {…}
将代表该参与者创建一个新的顶级任务。但如果当前函数不是 actor 隔离的,那么 Task {…}
也不会与任何给定的 actor 隔离,无论调用层次结构中之前发生了什么。
考虑:
@MainActor
class Foo {
func foo() {
// This is isolated to the main actor.
Bar().bar()
}
}
class Bar {
func bar() {
// This synchronous function is not isolated to any actor, so it will
// happen to just run on whatever thread from which it was called.
// If this is called from `Foo`’s `foo` method, isolated to the main actor,
// this will happen to also run on the main thread. But if called from
// somewhere not on the main thread, this will just run on the current
// thread, whatever it was.
Baz().baz()
}
}
class Baz {
func baz() {
// If called from the main thread, this will run on the main thread. If
// called from some other thread, this will run on whatever thread it was
// called.
//
// The key is, `baz` is not isolated to any particular actor…
Task {
// … thus, this is not isolated to any particular actor, either.
//
// Notably, even if `foo` called `bar`, and `bar` called `baz`, this
// will *not* run on the main actor.
}
}
}
在这个例子中,
foo
碰巧在主线程上调用了bar
,bar
碰巧也在主线程上调用了baz
。但是 baz
并未与任何特定参与者隔离,因此即使在本示例中它在主线程上启动,其 Task {…}
也不 与主要参与者隔离。请注意,我编写了三个非 async
函数。如果函数是 async
,细节会略有不同,但关键信息仍然成立:提供给 Task {…}
的闭包上下文将取决于调用者是否是参与者隔离的。
现在,也许您的引用引用了某些未与我们共享的特定代码片段,因此我无法进一步发表评论。但我会试图阻止任何人相信随机 Swift 并发代码随机查看调用层次结构以找出其上下文的想法。函数的上下文取决于当前函数(或其成员的类型)是否与特定参与者隔离。