我知道,作为某些同步过程的一部分,参与者内部的所有并行工作都会以某种方式更改为串行工作。我们可以看到,应该并行完成的 async let 工作在 Actor1 中按顺序完成,很可能是由于 Actor 的内部同步。但是,尽管 AnActor 内部同步,但 withTaskGroup 工作仍并行运行,但为什么呢?)
编辑:同时,我想说,我理解在使用 await 时从 Actor 内部调用时同步是如何工作的,但我不明白同步是如何在 Actor 内部工作的,以调用异步并行演员内部的任务。
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView().task {
//await AnActor().performAsyncTasks() // uncomment this alternately and run
//await Actor1().performAsyncTasks() // uncomment this alternately and run
}
}
}
}
actor Actor1 {
func performAsyncTasks() async {
async let _ = asyncTasks1() // this not running in parallel
async let _ = asyncTasks2() // this not running in parallel
}
func asyncTasks1() async {
for i in 1...10_000_0 {
print("In Task 1: \(i)")
}
}
func asyncTasks2() async {
for i in 1...10_000_0 {
print("In Task 2: \(i)")
}
}
} // the printed text are in series with Task 1 and Task 2 in console
actor AnActor {
var value = 0
func performAsyncTasks() async {
value = await withTaskGroup(of: Int.self) { group in
group.addTask { // this running in parallel, why?!
var value1 = 0
for _ in 1...10_000 {
print("Task1")
value1 += 1
}
return value1
}
group.addTask { // this running in parallel, why?!
var value2 = 0
for _ in 1...10_000 {
value2 += 1
print("Task2")
}
return value2
}
return await group.reduce(0, +)
}
print(value)
}
} // the printed text are mixed with Task 1 and Task 2 in console
考虑你的第一个例子:
actor Actor1 {
func performAsyncTasks() async {
async let _ = asyncTasks1() // this not running in parallel
async let _ = asyncTasks2() // this not running in parallel
}
func asyncTasks1() async {
for i in 1...10_000_0 {
print("In Task 1: \(i)")
}
}
func asyncTasks2() async {
for i in 1...10_000_0 {
print("In Task 2: \(i)")
}
}
}
你说:
我们可以看到,应该并行完成的
工作在async let
中按顺序完成Actor1
是的,一般
async let
可以让例程同时运行。但是,在这种情况下,这种情况不会在这里发生,因为这两个函数都被隔离到同一个 actor,并且没有 await
悬挂点。
有人说:
如果
是async let
… 的简写方式let _ = await asyncTasks1()
事实并非如此。请参阅SE-0317。
如果你想看到并行执行,你可以使用
async let
。你只需要把它们从当前的演员身上去掉就可以了。在这种情况下,请使用 nonisolated async
函数,如 SE-0338: 中所述
import os.log
actor Foo {
private let poi = OSSignposter(subsystem: "Test", category: .pointsOfInterest)
func bar() async throws {
async let value1 = first() // this IS running in concurrently
async let value2 = second() // this IS running in concurrently
let total = try await value1 + value2
print(total)
}
}
private extension Foo {
func first() async throws -> Int {
let state = poi.beginInterval(#function, id: poi.makeSignpostID())
defer { poi.endInterval(#function, state) }
return try await inefficientCount()
}
func second() async throws -> Int {
let state = poi.beginInterval(#function, id: poi.makeSignpostID())
defer { poi.endInterval(#function, state) }
return try await inefficientCount()
}
// This is `nonisolated` & `async` to get this off this actor. See SE-0338.
// Also, routines doing slow calculations should periodically `yield` and check for cancelation.
nonisolated func inefficientCount() async throws -> Int {
var value = 0
for i in 1...1_000_000_000 {
if i.isMultiple(of: 10_000_000) {
try Task.checkCancellation()
await Task.yield()
}
value += 1
}
return value
}
}
如果我在 Instruments 中对其进行分析,我可以看到它们并行运行:
您所看到的行为是由于您正在执行的异步任务(当该任务绑定到
Actor
时)的性质造成的。
iOS 底层的线程执行模型不允许抢占。也就是说,CPU 永远不会从任务中“被夺走”。当一个任务放弃 CPU 时,其他任务就有机会开始执行。
此代码:
for _ in 1...10_000 {
print("Task1")
value1 += 1
}
受 CPU 限制 - 在
for
循环完成之前,任何其他任务都没有机会在 Actor 上运行。
Async/Await 通常用于有异步操作的地方;例如,网络操作。
如果我们对您的功能进行小改动:
func asyncTasks1() async {
for i in 1...10_000_0 {
print("In Task 1: \(i)")
try? await Task.sleep(nanoseconds: 100)
}
}
func asyncTasks2() async {
for i in 1...10_000_0 {
print("In Task 2: \(i)")
try? await Task.sleep(nanoseconds: 100)
}
}
您将看到两个函数的输出混合在一起,因为每个函数在每个
print
之后都会放弃 CPU,从而允许参与者执行另一个函数,直到 it 放弃 CPU。
现在,至于为什么您会看到
withTaskGroup
的不同行为 - 这是因为任务组未绑定到 Actor
,即使您在绑定到 Actor
的函数中创建它。一个任务组可以使用多个线程来执行任务。这是它的主要功能,允许一系列独立的操作在全部完成(或取消)时通过简单的集合点来执行。
如果删除添加的
await sleep
并对任务组代码进行小更改:
func performAsyncTasks() async {
let a1=Actor1()
value = await withTaskGroup(of: Int.self) { group in
group.addTask { // this running in parallel, why?!
var value1 = 0
await a1.asyncTasks1()
return value1
}
group.addTask { // this running in parallel, why?!
var value2 = 0
await a1.asyncTasks2()
return value2
}
return await group.reduce(0, +)
}
您现在将看到两个循环按顺序完成,因为它们绑定到
Actor1
实例。