Luc*_*hae 3 concurrency ios async-await swift swiftui
我在使异步函数在后台线程中运行时遇到问题(以防止阻塞主线程)。
下面是一个运行时间大约为 5 秒的方法。根据我所了解到的情况,似乎创建该函数async并用await函数调用标记它就足够了。但它并没有按预期工作,并且用户界面仍然冻结。
编辑 既然据说 Swift 5.5 并发可以取代 DispatchQueue,我试图找到一种方法来仅使用 Async/Await 来做到这一点。
EDIT_2 我确实尝试删除 @MainActor 包装器,但它似乎仍然在主线程上运行。
@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)
在函数上写入async不会使其离开线程。你需要一个延续,并且你需要以某种方式真正离开线程。
您可以使用某些方法离开线程DispatchQueue.global(qos: .background).async {或使用Task.detached.
但最重要的部分是返回线程main,或者更具体地说是 Actor 的线程。
DispatchQueue.main.async是返回主线程的“旧”方式,不应该与async await. Apple 已提供CheckedContinuation并UncheckedContinuation用于此目的。
认识async/await可以再详细说明一下。
\nimport 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}\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
1023 次 |
| 最近记录: |