行业知识
在 Swift 中使用 async let 并发运行后台任务
2026-11-04 08:50:52
来源:互联网转载
文章目录
- 前言
- 长期运行的任务阻塞了UI
- 使用 async/await 在后台执行任务
- 在后台执行多个任务
- 使用 "async let " 下载多个文件
- 结论
前言
Async/await 语法是在 Swift 5.5 引入的,在 WWDC 2021中的 Meet async/await in Swift 对齐进行了介绍。它是编写异步代码的一种更可读的方式,比调度队列和回调函数更容易理解。Async/await 语法与其他编程语言(如 C# 或 JavaScript)中使用的语法类似。使用 "async let "是为了并行的运行多个后台任务,并等待它们的综合结果。
Swift 异步编程是一种编写允许某些任务并发运行而不是按顺序运行的代码的方法。这可以提高应用程序的性能,允许它同时执行多个任务,但更重要的是,它可以用来确保用户界面对用户输入的响应,同时任务在后台线程上执行。
长期运行的任务阻塞了UI
在一个同步的程序中,代码以线性的、从上到下的方式运行。程序等待当前任务完成后再进入下一任务。这在用户界面(UI)方面会产生问题,因为如果一个长期运行的任务被同步执行,程序就会阻塞,UI就会变得没有反应,直到任务完成。
下面的代码模拟了一个长期运行的任务,如以同步方式下载一个文件,其结果是UI 变得没有反应,直到任务完成。这样的用户体验是不可接受的。
Model:
struct DataFile : Identifiable, Equatable { var id: Int var fileSize: Int var downloadedSize = 0 var isDownloading = false init(id: Int, fileSize: Int) { self.id = id self.fileSize = fileSize } var progress: Double { return Double(self.downloadedSize) / Double(self.fileSize) } mutating func increment() { if downloadedSizeViewModel:
class DataFileViewModel: ObservableObject { @Published private(set) var file: DataFile init() { self.file = DataFile(id: 1, fileSize: 10) } func downloadFile() { file.isDownloading = true for _ in 0.. file.increment() usleep(300000) } file.isDownloading = false } func reset() { self.file = DataFile(id: 1, fileSize: 10) }} @ObservedObject private var dataFiles: DataFileViewModel init() { dataFiles = DataFileViewModel() } var body: some View { VStack { /// 从文末源代码获取其实现 TitleView(title: ["Synchronous"]) Button("Download All") { dataFiles.downloadFile() } .buttonStyle(BlueButtonStyle()) .disabled(dataFiles.file.isDownloading) HStack(spacing: 10) { Text("File 1:") ProgressView(value: dataFiles.file.progress) .frame(width: 180) Text("\((dataFiles.file.progress * 100), specifier: "%0.0F")%") ZStack { Color.clear .frame(width: 30, height: 30) if dataFiles.file.isDownloading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .blue)) } } } .padding() Spacer().frame(height: 200) Button("Reset") { dataFiles.reset() } .buttonStyle(BlueButtonStyle()) Spacer() } .padding() }} @Published private(set) var file: DataFile init() { self.file = DataFile(id: 1, fileSize: 10) } func downloadFile() async - Int { await MainActor.run { file.isDownloading = true } for _ in 0.. await MainActor.run { file.increment() } usleep(300000) } await MainActor.run { file.isDownloading = false } return 1 } func reset() { self.file = DataFile(id: 1, fileSize: 10) }} @ObservedObject private var dataFiles: DataFileViewModel2 @State var fileCount = 0 init() { dataFiles = DataFileViewModel2() } var body: some View { VStack { TitleView(title: ["Asynchronous"]) Button("Download All") { Task { let num = await dataFiles.downloadFile() fileCount += num } } .buttonStyle(BlueButtonStyle()) .disabled(dataFiles.file.isDownloading) Text("Files Downloaded: \(fileCount)") HStack(spacing: 10) { Text("File 1:") ProgressView(value: dataFiles.file.progress) .frame(width: 180) Text("\((dataFiles.file.progress * 100), specifier: "%0.0F")%") ZStack { Color.clear .frame(width: 30, height: 30) if dataFiles.file.isDownloading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .blue)) } } } .padding() Spacer().frame(height: 200) Button("Reset") { dataFiles.reset() } .buttonStyle(BlueButtonStyle()) Spacer() } .padding() }} @Published private(set) var files: [DataFile] @Published private(set) var fileCount = 0 init() { files = [ DataFile(id: 1, fileSize: 10), DataFile(id: 2, fileSize: 20), DataFile(id: 3, fileSize: 5) ] } var isDownloading : Bool { files.filter { .isDownloading }.count 0 } func downloadFiles() async { for index in files.indices { let num = await downloadFile(index) await MainActor.run { fileCount += num } } } private func downloadFile(_ index: Array.Index) async -> Int { await MainActor.run { files[index].isDownloading = true } for _ in 0.. await MainActor.run { files[index].increment() } usleep(300000) } await MainActor.run { files[index].isDownloading = false } return 1 } func reset() { files = [ DataFile(id: 1, fileSize: 10), DataFile(id: 2, fileSize: 20), DataFile(id: 3, fileSize: 5) ] }} @ObservedObject private var dataFiles: DataFileViewModel3 init() { dataFiles = DataFileViewModel3() } var body: some View { VStack { TitleView(title: ["Asynchronous", "(multiple Files)"]) Button("Download All") { Task { await dataFiles.downloadFiles() } } .buttonStyle(BlueButtonStyle()) .disabled(dataFiles.isDownloading) Text("Files Downloaded: \(dataFiles.fileCount)") ForEach(dataFiles.files) { file in HStack(spacing: 10) { Text("File \(file.id):") ProgressView(value: file.progress) .frame(width: 180) Text("\((file.progress * 100), specifier: "%0.0F")%") ZStack { Color.clear .frame(width: 30, height: 30) if file.isDownloading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .blue)) } } } } .padding() Spacer().frame(height: 150) Button("Reset") { dataFiles.reset() } .buttonStyle(BlueButtonStyle()) Spacer() } .padding() }} for index in files.indices { let num = await downloadFile(index) await MainActor.run { fileCount += num } } } async let num1 = await downloadFile(0) async let num2 = await downloadFile(1) async let num3 = await downloadFile(2) let (result1, result2, result3) = await (num1, num2, num3) await MainActor.run { fileCount = result1 + result2 + result3 } } @Published private(set) var files: [DataFile] @Published private(set) var fileCount = 0 init() { files = [ DataFile(id: 1, fileSize: 10), DataFile(id: 2, fileSize: 20), DataFile(id: 3, fileSize: 5) ] } var isDownloading : Bool { files.filter { .isDownloading }.count 0 } func downloadFiles() async { async let num1 = await downloadFile(0) async let num2 = await downloadFile(1) async let num3 = await downloadFile(2) let (result1, result2, result3) = await (num1, num2, num3) await MainActor.run { fileCount = result1 + result2 + result3 } } private func downloadFile(_ index: Array.Index) async -> Int { await MainActor.run { files[index].isDownloading = true } for _ in 0.. await MainActor.run { files[index].increment() } usleep(300000) } await MainActor.run { files[index].isDownloading = false } return 1 } func reset() { files = [ DataFile(id: 1, fileSize: 10), DataFile(id: 2, fileSize: 20), DataFile(id: 3, fileSize: 5) ] }} @ObservedObject private var dataFiles: DataFileViewModel4 init() { dataFiles = DataFileViewModel4() } var body: some View { VStack { TitleView(title: ["Parallel", "(multiple Files)"]) Button("Download All") { Task { await dataFiles.downloadFiles() } } .buttonStyle(BlueButtonStyle()) .disabled(dataFiles.isDownloading) Text("Files Downloaded: \(dataFiles.fileCount)") ForEach(dataFiles.files) { file in HStack(spacing: 10) { Text("File \(file.id):") ProgressView(value: file.progress) .frame(width: 180) Text("\((file.progress * 100), specifier: "%0.0F")%") ZStack { Color.clear .frame(width: 30, height: 30) if file.isDownloading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .blue)) } } } } .padding() Spacer().frame(height: 150) Button("Reset") { dataFiles.reset() } .buttonStyle(BlueButtonStyle()) Spacer() } .padding() }}
下一篇:联通网络怎么开服务器?