SwiftUI有this.task(priority:_:)视图修饰符。它运行异步代码。默认优先级为userInitiated。没有提到这是在哪个线程上运行的。只是通过测试似乎在后台线程上运行,但是:假设总是在非主线程中运行是否安全?
.task(priority:_:)
userInitiated
91zkwejq1#
我不知道你是如何得出结论,这不是在主线程上,但当我在调试器上运行它时,它是在主队列上:
这是有道理的,因为body与主参与者隔离,我希望它的task也代表当前参与者(主参与者)运行。(如果你需要从当前的参与者那里获得一些东西,你通常会使用一个分离的任务。不过,您只需要为缓慢的同步工作单元执行此操作。)底线是,body的SwiftUI .task视图修饰符与主actor隔离。(FWIW,我也以多种方式验证了这一点。简而言之,如果你真的需要确保一些东西离开主参与者(例如,在.task视图修饰符中运行一些缓慢和同步的东西),请确保将其放在一个单独的参与者上,在一个分离的任务中,或一些等效的模式中。其他一些观察:1.顺便说一句,我们不再关注Swift并发中的线程。它可能会产生误导性的结果。Swift并发性使我们远离了线程级推理。在未来,Swift 6将删除许多我们用来以编程方式查询Thread信息的API。底线是,不要再担心线程,而要关注参与者隔离。如果你真的想了解有关Swift并发线程模型的更多信息,请参阅WWDC 2021视频Swift concurrency: Behind the scenes。它没有直接触及.task视图修饰符的问题,但它将帮助您理解为什么我们不再像以前那样关注线程。1.就我个人而言,在我的.task视图修饰符中,我通常只是调用一些async方法,在这种情况下,.task视图修饰符使用的actor在很大程度上变得无关紧要(因为我们只是await调用,它挂起当前actor的执行,释放它去做其他事情)。1.同样,在GCD世界中,我们浪费了大量的精力来担心我们是否从正确的队列中调用了某些特定的方法(如果我们搞砸了,还要处理运行时错误)。但是在Swift并发中,我们将被调用的函数隔离到适当的actor,现在如果我们没有正确调用它,我们会得到编译时警告(例如,当我们使用“Complete”的“严格并发检查”构建设置时,双重检查Sendable类型,当我们跨越actor边界时忽略包含所需的await,等等)。在评论中,你提到你在“检查线程...在async函数中从任务修饰符“”调用。是的,在Swift 5.7中实现的SE-0338告诉我们,非隔离的async函数“不会在任何actor的执行器上运行”。因此,除非它是一个以某种方式显式隔离到主参与者的函数,否则它不会在主参与者上运行。
body
task
.task
Thread
async
await
Sendable
o4hqfura2#
使用async/await和.task修饰符时,你必须以不同的方式考虑线程。虽然它是从主线程开始的,但每一行await代码都会执行一些操作,当它完成时,它会移动到下一行。等待行运行的实际线程取决于它的参与者。如果它是View结构体中的异步函数,那么它就是主线程,因为它被标记为@MainActor,因此主线程将在await行期间被阻塞。如果它在不同的结构或类中声明,那么它应该是一个随机的后台线程。你也可以在.task中通过使用一个子线程Task { }来获取一个后台线程,即使显示这个View结构的屏幕消失了,这个子线程Task仍然会被取消,就像.task一样,小心不要使用Task.detatched,因为这样做不会被取消,可能会导致崩溃。在View函数中获取后台线程的另一种方法是将其标记为nonisolated。在这个函数中放置一个断点来检查它是否在后台线程上。一些在后台线程中执行异步函数的类或actor使用MainActor.run或Task { @MainActor in切换到主线程,例如。更新@Published。不,我们不再需要Xcode 15/iOS 17中的新@Observable提取器类,例如。
View
@MainActor
Task { }
Task
Task.detatched
nonisolated
MainActor.run
Task { @MainActor in
@Published
@Observable
@Observable class Test { var counter = 0 func update() async { do { try await Task.sleep(for: .seconds(3)) counter+=1 // no longer need to wrap this in Task.mainActor { } to set this anymore } catch { } } }
2条答案
按热度按时间91zkwejq1#
我不知道你是如何得出结论,这不是在主线程上,但当我在调试器上运行它时,它是在主队列上:
这是有道理的,因为
body
与主参与者隔离,我希望它的task
也代表当前参与者(主参与者)运行。(如果你需要从当前的参与者那里获得一些东西,你通常会使用一个分离的任务。不过,您只需要为缓慢的同步工作单元执行此操作。)底线是,
body
的SwiftUI.task
视图修饰符与主actor隔离。(FWIW,我也以多种方式验证了这一点。简而言之,如果你真的需要确保一些东西离开主参与者(例如,在
.task
视图修饰符中运行一些缓慢和同步的东西),请确保将其放在一个单独的参与者上,在一个分离的任务中,或一些等效的模式中。其他一些观察:
1.顺便说一句,我们不再关注Swift并发中的线程。它可能会产生误导性的结果。Swift并发性使我们远离了线程级推理。在未来,Swift 6将删除许多我们用来以编程方式查询
Thread
信息的API。底线是,不要再担心线程,而要关注参与者隔离。如果你真的想了解有关Swift并发线程模型的更多信息,请参阅WWDC 2021视频Swift concurrency: Behind the scenes。它没有直接触及
.task
视图修饰符的问题,但它将帮助您理解为什么我们不再像以前那样关注线程。1.就我个人而言,在我的
.task
视图修饰符中,我通常只是调用一些async
方法,在这种情况下,.task
视图修饰符使用的actor在很大程度上变得无关紧要(因为我们只是await
调用,它挂起当前actor的执行,释放它去做其他事情)。1.同样,在GCD世界中,我们浪费了大量的精力来担心我们是否从正确的队列中调用了某些特定的方法(如果我们搞砸了,还要处理运行时错误)。但是在Swift并发中,我们将被调用的函数隔离到适当的actor,现在如果我们没有正确调用它,我们会得到编译时警告(例如,当我们使用“Complete”的“严格并发检查”构建设置时,双重检查
Sendable
类型,当我们跨越actor边界时忽略包含所需的await
,等等)。在评论中,你提到你在“检查线程...在
async
函数中从任务修饰符“”调用。是的,在Swift 5.7中实现的SE-0338告诉我们,非隔离的
async
函数“不会在任何actor的执行器上运行”。因此,除非它是一个以某种方式显式隔离到主参与者的函数,否则它不会在主参与者上运行。o4hqfura2#
使用async/await和
.task
修饰符时,你必须以不同的方式考虑线程。虽然它是从主线程开始的,但每一行await代码都会执行一些操作,当它完成时,它会移动到下一行。等待行运行的实际线程取决于它的参与者。如果它是View
结构体中的异步函数,那么它就是主线程,因为它被标记为@MainActor
,因此主线程将在await行期间被阻塞。如果它在不同的结构或类中声明,那么它应该是一个随机的后台线程。你也可以在.task
中通过使用一个子线程Task { }
来获取一个后台线程,即使显示这个View结构的屏幕消失了,这个子线程Task
仍然会被取消,就像.task
一样,小心不要使用Task.detatched
,因为这样做不会被取消,可能会导致崩溃。在View
函数中获取后台线程的另一种方法是将其标记为nonisolated
。在这个函数中放置一个断点来检查它是否在后台线程上。一些在后台线程中执行异步函数的类或actor使用MainActor.run
或Task { @MainActor in
切换到主线程,例如。更新@Published
。不,我们不再需要Xcode 15/iOS 17中的新@Observable
提取器类,例如。