标签: swift-concurrency

如何正确取消 Swift async/await 函数

我看过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

19
推荐指数
1
解决办法
1万
查看次数

转换不可发送的函数值可能会引入数据竞争

我有一段简单的代码:

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”可能会引入数据争用

这是为什么?我该如何解决它?

swift swift5 swiftui swift-concurrency

18
推荐指数
1
解决办法
4195
查看次数

15
推荐指数
1
解决办法
2985
查看次数

为什么 SwiftUI 视图模型应该用 @MainActor 注解?

我一直在观看 Apple 在 WWDC21 上的并发演讲,并阅读了大量有关 Apple 并发更新的文章;然而,我无法理解一件事:为什么人们会提供指导,让你应该用它来注释视图模型@MainActor?据我所知,通过将视图模型属性设置为视图中的@StateObject或,它会自动变为. 那么如果是这样的话,为什么人们仍然建议将视图模型注释为?@ObservedObject@MainActor@MainActor

\n

对于上下文,以下是我读过的一些引用此内容的文章:

\n
    \n
  1. https://www.hackingwithswift.com/quick-start/concurrency/understanding-how-global-actor-inference-works
  2. \n
  3. https://www.hackingwithswift.com/books/concurrency/how-to-use-mainactor-to-run-code-on-the-main-queue
  4. \n
  5. https://peterfriese.dev/swiftui-concurrency-essentials-part1/
  6. \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
\n

总而言之,如果指导会自动为我们处理,我有点困惑。特别是因为我不知道在 SwiftUI 中有一个不是@ObservableObject.

\n …

actor ios swift swiftui swift-concurrency

11
推荐指数
1
解决办法
6785
查看次数

Swift 5.5 并发:如何序列化异步任务以用 maxConcurrentOperationCount = 1 替换 OperationQueue?

I\xe2\x80\x99m 目前正在迁移我的应用程序以使用 Swift 中的并发模型。我想序列化任务以确保它们一个接一个地执行(无并行性)。在我的用例中,我想监听通知中心发布的通知,并在每次发布新通知时执行任务。但我想确保之前没有任务正在运行。这相当于使用 maxConcurrentOperationCount = 1 的操作队列。

\n

例如,我\xe2\x80\x99m 在我的应用程序中使用 CloudKit 和 Core Data,并使用持久历史记录来确定存储中发生了哪些更改。\n在此将本地存储同步到云示例代码中,Apple 使用用于处理历史处理任务的操作队列(在CoreDataStack中)。此OperationQueue 的最大操作数设置为1。

\n
private lazy var historyQueue: OperationQueue = {\n    let queue = OperationQueue()\n    queue.maxConcurrentOperationCount = 1\n    return queue\n}()\n
Run 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}\n
Run 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

10
推荐指数
1
解决办法
3927
查看次数

如何使用 Swift Concurrency 在后台执行 CPU 密集型任务而不阻塞 UI 更新?

我有一个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)

swift swiftui swift-concurrency

10
推荐指数
2
解决办法
1253
查看次数

Swift 5.5 中使用 async/await 的循环函数

我想在函数完成后 5 秒继续触发它。

以前我会在函数末尾使用它:

Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { self.function() }
Run Code Online (Sandbox Code Playgroud)

但我想用Swift 5.5async/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 …

async-await swift swift5 swiftui swift-concurrency

9
推荐指数
2
解决办法
6913
查看次数

如何将 DispatchQueue debounce 转换为 Swift Concurrency 任务?

我有一个现有的防抖实用程序,使用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 秒,无论调用多少次。闭包被传递到 …

grand-central-dispatch debouncing swift swift-concurrency

9
推荐指数
1
解决办法
2902
查看次数

withThrowingTaskGroup - “try”表达式中不会调用抛出函数

问题:

我有以下显示警告的功能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)

ios async-await swift swift-concurrency

9
推荐指数
1
解决办法
1809
查看次数

使 Swift 并发中的任务串行运行

我有一个基于文档的应用程序,它使用结构作为其主要数据/模型。由于模型是它(的子类)的属性,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)

multithreading swift swift-concurrency

9
推荐指数
1
解决办法
3384
查看次数