我看过Explore structured concurrency in Swift视频和我能找到的其他相关视频/文章/书籍(Sundell 的 swift、用 swift 进行黑客攻击、Ray Renderlich),但所有示例都非常琐碎 - 异步函数通常只有 1 个异步调用。这在现实生活中的代码中应该如何工作?
例如:
...
task = Task {
var longRunningWorker: LongRunningWorker? = nil
do {
var fileURL = state.fileURL
if state.needsCompression {
longRunningWorker = LongRunningWorker(inputURL: fileURL)
fileURL = try await longRunningWorker!.doAsyncWork()
}
let urls = try await ApiService.i.fetchUploadUrls()
if let image = state.image, let imageData = image.jpegData(compressionQuality: 0.8) {
guard let imageUrl = urls.signedImageUrl else {
fatalError("Cover art supplied but art upload URL is nil")
} …Run Code Online (Sandbox Code Playgroud) ios async-await swift structured-concurrency swift-concurrency
我有一段简单的代码:
struct ContentView: View {
var body: some View {
Text("Hello world!")
.task {
await myAsyncFunc()
}
}
private func myAsyncFunc() async {}
}
Run Code Online (Sandbox Code Playgroud)
这编译完全没问题。但是,如果我用以下内容替换任务:
.task(myAsyncFunc)
Run Code Online (Sandbox Code Playgroud)
它不起作用,并给我以下错误:
将不可发送的函数值转换为“@Sendable () async -> Void”可能会引入数据争用
这是为什么?我该如何解决它?
之间有什么区别:
Task { await MainActor.run { ... } }
Run Code Online (Sandbox Code Playgroud)
和
Task { @MainActor in ... }
Run Code Online (Sandbox Code Playgroud) 我一直在观看 Apple 在 WWDC21 上的并发演讲,并阅读了大量有关 Apple 并发更新的文章;然而,我无法理解一件事:为什么人们会提供指导,让你应该用它来注释视图模型@MainActor?据我所知,通过将视图模型属性设置为视图中的@StateObject或,它会自动变为. 那么如果是这样的话,为什么人们仍然建议将视图模型注释为?@ObservedObject@MainActor@MainActor
对于上下文,以下是我读过的一些引用此内容的文章:
\n摘自第一个链接:
\n\n\n[A]任何使用带有 @MainActor 的属性包装器作为其包装值的结构或类将自动成为 @MainActor。这就是为什么 @StateObject 和 @ObservedObject 在使用它们的 SwiftUI 视图上传达主要参与者的原因 \xe2\x80\x93 如果您在 SwiftUI 视图中使用这两个属性包装器中的任何一个,整个视图也会变成 @MainActor。
\n
摘自第二个链接:
\n\n\n[W]每当你在视图中使用 @StateObject 或 @ObservedObject 时,Swift 都会确保整个视图在主要参与者上运行,这样你就不会意外地尝试以危险的方式发布 UI 更新。更好的是,无论您使用什么属性包装器,SwiftUI 视图的 body 属性始终在主要参与者上运行。
\n
\n\n这是否意味着您不需要显式地将 @MainActor 添加到可观察对象?好吧,不 \xe2\x80\x93 在这些类中使用 @MainActor 仍然有好处,尤其是如果它们使用 async/await 来完成自己的异步工作,例如从服务器下载数据。
\n
总而言之,如果指导会自动为我们处理,我有点困惑。特别是因为我不知道在 SwiftUI 中有一个不是@ObservableObject.
I\xe2\x80\x99m 目前正在迁移我的应用程序以使用 Swift 中的并发模型。我想序列化任务以确保它们一个接一个地执行(无并行性)。在我的用例中,我想监听通知中心发布的通知,并在每次发布新通知时执行任务。但我想确保之前没有任务正在运行。这相当于使用 maxConcurrentOperationCount = 1 的操作队列。
\n例如,我\xe2\x80\x99m 在我的应用程序中使用 CloudKit 和 Core Data,并使用持久历史记录来确定存储中发生了哪些更改。\n在此将本地存储同步到云示例代码中,Apple 使用用于处理历史处理任务的操作队列(在CoreDataStack中)。此OperationQueue 的最大操作数设置为1。
\nprivate lazy var historyQueue: OperationQueue = {\n let queue = OperationQueue()\n queue.maxConcurrentOperationCount = 1\n return queue\n}()\nRun Code Online (Sandbox Code Playgroud)\n当收到 Core Data 通知时,新任务将添加到该串行操作队列中。因此,如果收到多个通知,它们都会以串行方式一个接一个地执行。
\n@objc\nfunc storeRemoteChange(_ notification: Notification) {\n // Process persistent history to merge changes from other coordinators.\n historyQueue.addOperation {\n self.processPersistentHistory()\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n在此加载和显示大数据源示例代码中,Apple 使用任务来处理历史更改(在 QuakesProvider 中)。
\n// Observe Core Data remote change notifications on the queue where …Run Code Online (Sandbox Code Playgroud) core-data nsoperationqueue grand-central-dispatch swift swift-concurrency
我有一个ObservableObject可以完成 CPU 密集型繁重工作的程序:
import Foundation
import SwiftUI
@MainActor
final class Controller: ObservableObject {
@Published private(set) var isComputing: Bool = false
func compute() {
if isComputing { return }
Task {
heavyWork()
}
}
func heavyWork() {
isComputing = true
sleep(5)
isComputing = false
}
}
Run Code Online (Sandbox Code Playgroud)
我使用 aTask在后台使用新的并发功能进行计算。这需要使用该@MainActor属性来确保所有 UI 更新(此处与该属性相关isComputing)都在主要参与者上执行。
然后,我有以下视图,其中显示一个计数器和一个启动计算的按钮:
struct ContentView: View {
@StateObject private var controller: Controller
@State private var counter: Int = 0
init() {
_controller = StateObject(wrappedValue: Controller()) …Run Code Online (Sandbox Code Playgroud) 我想在函数完成后 5 秒继续触发它。
以前我会在函数末尾使用它:
Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { self.function() }
Run Code Online (Sandbox Code Playgroud)
但我想用Swift 5.5的async/await.
如果我使用这样的东西:
func loadInfo() async {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
await loadInfo()
}
Run Code Online (Sandbox Code Playgroud)
我收到一条警告,说它Function call causes an infinite recursion并不是真正可以取消的。
这编译得很好:
func loadInfo() async {
Task {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
if Task.isCancelled {
print("Cancelled")
}
else
{
print("Not cancelled")
await loadInfo()
}
}
}
Run Code Online (Sandbox Code Playgroud)
尽管它每 5 …
我有一个现有的防抖实用程序,使用DispatchQueue. 它接受一个闭包并在达到时间阈值之前执行它。它可以这样使用:
let limiter = Debouncer(limit: 5)
var value = ""
func sendToServer() {
limiter.execute {
print("\(Date.now.timeIntervalSince1970): Fire! \(value)")
}
}
value.append("h")
sendToServer() // Waits until 5 seconds
value.append("e")
sendToServer() // Waits until 5 seconds
value.append("l")
sendToServer() // Waits until 5 seconds
value.append("l")
sendToServer() // Waits until 5 seconds
value.append("o")
sendToServer() // Waits until 5 seconds
print("\(Date.now.timeIntervalSince1970): Last operation called")
// 1635691696.482115: Last operation called
// 1635691701.859087: Fire! hello
Run Code Online (Sandbox Code Playgroud)
请注意,它不是Fire!多次调用,而是在最后一次使用上一个任务的值后仅 5 秒调用。该Debouncer实例配置为将队列中的最后一个任务保留 5 秒,无论调用多少次。闭包被传递到 …
我有以下显示警告的功能No calls to throwing functions occur within 'try' expression
f1?func f1() async throws {
try await withThrowingTaskGroup(of: Int.self) { group in //No calls to throwing functions occur within 'try' expression
group.addTask(priority: .high) {
throw NSError()
}
}
}
Run Code Online (Sandbox Code Playgroud) 我有一个基于文档的应用程序,它使用结构作为其主要数据/模型。由于模型是它(的子类)的属性,NSDocument因此需要从主线程访问。到目前为止一切都很好。
但对数据的某些操作可能需要相当长的时间,我想为用户提供一个进度条。这就是问题开始的地方。特别是当用户从 GUI 快速连续启动两个操作时。
如果我同步(或以“正常”方式)在模型上运行操作,Task {}我会得到正确的串行行为,但主线程被阻止,因此我无法显示进度条。(选项A)
如果我在闭包中对模型运行操作,Task.detached {}我可以更新进度条,但根据模型上操作的运行时间,用户的第二个操作可能会在第一个操作之前完成,从而导致无效/意外状态模型的。这是由于await分离任务中需要的语句(我认为)。(选项B)。
所以我想要 a) 释放主线程来更新 GUI,b) 确保每个任务在另一个(排队的)任务开始之前运行完全完成。使用后台串行调度队列很有可能实现这一点,但我正在尝试切换到新的 Swift 并发系统,该系统也用于在访问模型之前执行任何准备工作。
我尝试使用全局参与者,因为这似乎是某种串行后台队列,但它也需要await语句。尽管模型中出现意外状态的可能性降低了,但这仍然是可能的。
我写了一些小代码来演示这个问题:
该模型:
struct Model {
var doneA = false
var doneB = false
mutating func updateA() {
Thread.sleep(forTimeInterval: 5)
doneA = true
}
mutating func updateB() {
Thread.sleep(forTimeInterval: 1)
doneB = true
}
}
Run Code Online (Sandbox Code Playgroud)
和文档(省略标准覆盖NSDocument):
@globalActor
struct ModelActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
class Document: …Run Code Online (Sandbox Code Playgroud)