在后台线程中运行异步函数时遇到问题(并发)

Luc*_*hae 3 concurrency ios async-await swift swiftui

我在使异步函数在后台线程中运行时遇到问题(以防止阻塞主线程)。

下面是一个运行时间大约为 5 秒的方法。根据我所了解到的情况,似乎创建该函数async并用await函数调用标记它就足够了。但它并没有按预期工作,并且用户界面仍然冻结。

编辑 既然据说 Swift 5.5 并发可以取代 DispatchQueue,我试图找到一种方法来仅使用 Async/Await 来做到这一点。

EDIT_2 我确实尝试删除 @MainActor 包装器,但它似乎仍然在主线程上运行。

NumberManager.swift

@MainActor class NumberManager: ObservableObject {
@Published var numbers: [Double]?    

func generateNumbers() async  {

        var numbers = [Double]()
        numbers = (1...10_000_000).map { _ in Double.random(in: -10...10) }
        self.numbers = numbers
    // takes about 5 seconds to run...
} }
Run Code Online (Sandbox Code Playgroud)

内容视图

struct ContentView: View {
@StateObject private var numberManager = NumberManager()

var body: some View{
    TabView{
        VStack{
            DetailView(text: isNumbersValid ? "First number is: \(numberManager.numbers![0])" : nil)
                .onAppear() {
                    Task {
                        // Runs in the main thread, freezing up the UI until it completes. 
                        await numberManager.generateNumbers()
                    }
                }

        }
        .tabItem {
            Label("One", systemImage: "list.dash")
        }
        Text("Hello")
            .tabItem {
                Label("Two", systemImage: "square.and.pencil")
            }
    }

}

var isNumbersValid: Bool{
    numberManager.numbers != nil && numberManager.numbers?.count != 0
} }
Run Code Online (Sandbox Code Playgroud)

我尝试过的...

我尝试了一些方法,但使其在后台运行的唯一方法是更改​​函数,如下所示。但我知道除非绝对必要,否则应该避免使用 Task.detached,而且我认为这不是正确的用例。

    func generateNumbers() async  {
    Task.detached {
        var numbers = [Double]()
        numbers = (1...10_000_000).map { _ in Double.random(in: -10...10) }
        await MainActor.run { [numbers] in
            self.numbers = numbers
        }

    }
Run Code Online (Sandbox Code Playgroud)

lor*_*sum 6

在函数上写入async不会使其离开线程。你需要一个延续,并且你需要以某种方式真正离开线程。

\n

您可以使用某些方法离开线程DispatchQueue.global(qos: .background).async {或使用Task.detached.

\n

但最重要的部分是返回线程main,或者更具体地说是 Actor 的线程。

\n

DispatchQueue.main.async是返回主线程的“旧”方式,不应该与async await. Apple 已提供CheckedContinuationUncheckedContinuation用于此目的。

\n

认识async/await可以再详细说明一下。

\n
import SwiftUI\n\nstruct ConcurrentSampleView: View {\n    //Solution\n    @StateObject var vm: AsyncNumberManager = .init()\n    //Just to create a project that can show both scenarios.\n    //@StateObject var vm: NumberManager = .init()\n\n    @State var isLoading: Bool = false\n    var body: some View {\n        HStack{\n            //Just to visualize the thread being released\n            //If you use NumberManager the ProgressView won\'t appear\n            //If you use AsyncNumberManager the ProgressView WILL appear\n            if isLoading{\n                ProgressView()\n            }\n            \n            Text(vm.numbers == nil ? "nil" : "\\(vm.numbers?.count.description ?? "")")\n        }\n        //.task is better for iOS 15+\n        .onAppear() {\n            Task{\n                isLoading = true\n                await vm.generateNumbers()\n                isLoading = false\n            }\n        }\n        \n    }\n}\n\nstruct ConcurrentSampleView_Previews: PreviewProvider {\n    static var previews: some View {\n        ConcurrentSampleView()\n    }\n}\n\n@MainActor\nclass AsyncNumberManager: ObservableObject {\n    @Published var numbers: [Double]?\n    \n    func generateNumbers() async  {\n        numbers = await concurrentGenerateNumbers()\n    }\n    \n    private func concurrentGenerateNumbers() async -> [Double]  {\n        typealias Cont = CheckedContinuation<[Double], Never>\n        return await withCheckedContinuation { (cont: Cont) in\n            // This is the asynchronous part, have the operation leave the current actor\'s thread.\n            //Change the priority as needed\n            //https://developer.apple.com/documentation/swift/taskpriority\n            Task.detached(priority: .utility){\n                var numbers = [Double]()\n                numbers = (1...10_000_000).map { _ in Double.random(in: -10...10) }\n                //This tells the function to return to the actor\'s thread\n                cont.resume(returning: numbers)\n            }\n        }\n    }\n    //Or something like this it just depends on the true scenario\n    private func concurrentGenerateNumbers2() async -> [Double]  {\n        // This is the asynchronous part, have the operation leave the actor\'s thread\n        //Change the priority as needed\n            //https://developer.apple.com/documentation/swift/taskpriority\n        return await Task.detached(priority: .utility){\n            var numbers = [Double]()\n            numbers = (1...10_000_000).map { _ in Double.random(in: -10...10) }\n            return numbers\n        }.value\n        \n    }\n    \n}\n//Incorrect way of applying async/await. This doesn\'t actually leave the thread or mark when to return. Left here to highlight both scenarios in a reproducible example.\n@MainActor\nclass NumberManager: ObservableObject {\n    @Published var numbers: [Double]?\n    \n    func generateNumbers() async  {\n        \n        var numbers = [Double]()\n        numbers = (1...10_000_000).map { _ in Double.random(in: -10...10) }\n        self.numbers = numbers\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n