如何在Swift 中使用 async let 并发运行后台任务

 更新时间:2023年06月26日 11:10:34   作者:Swift社区  
Swift 异步编程是一种编写允许某些任务并发运行而不是按顺序运行的代码的方法,这篇文章主要介绍了在Swift中使用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 downloadedSize < fileSize {
            downloadedSize += 1
        }
    }
}

ViewModel:

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.fileSize {
            file.increment()
            usleep(300000)
        }
        file.isDownloading = false
    }
    func reset() {
        self.file = DataFile(id: 1, fileSize: 10)
    }
}

View:

struct TestView1: View {
    @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()
    }
}

使用 async/await 在后台执行任务

将 ViewModel 中的downloadFile方法修改为异步的。请注意,由于DataFile模型是被视图监听的,对模型的任何改变都需要在UI线程上执行。这是通过使用 MainActor 队列来完成的,即用MainActor.run包裹所有的模型更新。

ViewModel

class DataFileViewModel2: ObservableObject {
    @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..<file.fileSize {
            await MainActor.run {
                file.increment()
            }
            usleep(300000)
        }
        await MainActor.run {
            file.isDownloading = false
        }
        return 1
    }
    func reset() {
        self.file = DataFile(id: 1, fileSize: 10)
    }
}

View:

struct TestView2: View {
    @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()
    }
}

在后台执行多个任务

现在我们有一个文件在后台下载,UI显示进度,让我们把它改为多个文件。ViewModel被改为持有一个DataFiles数组,而不是一个单一的文件。添加一个downloadFiles方法来遍历所有文件并下载每一个。

视图被绑定到DataFiles数组,并更新显示每个文件的下载进度。下载按钮被绑定到异步的downloadFiles中。

ViewModel:

class DataFileViewModel3: ObservableObject {
    @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 { $0.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<DataFile>.Index) async -> Int {
        await MainActor.run {
            files[index].isDownloading = true
        }
        for _ in 0..<files[index].fileSize {
            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)
        ]
    }
}

View:

struct TestView3: View {
    @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()
    }
}

使用 "async let " 下载多个文件

使用 "async let "来模拟并发下载多个文件的情况

上面的代码可以被改进,以并行地执行多个下载,因为每个任务都是独立于其他任务的。在Swift并发中,这是用async let实现的,它用一个承诺立即给一个变量赋值,允许代码执行下一行代码。然后,代码等待这些承诺,等待最终结果的完成。

async/await:

    func downloadFiles() async {
        for index in files.indices {
            let num = await downloadFile(index)
            await MainActor.run {
                fileCount += num
            }
        }
    }

async let

    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
        }
    }

ViewModel

class DataFileViewModel4: ObservableObject {
    @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 { $0.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<DataFile>.Index) async -> Int {
        await MainActor.run {
            files[index].isDownloading = true
        }
        for _ in 0..<files[index].fileSize {
            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)
        ]
    }
}

View

struct TestView4: View {
    @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()
    }
}

结论

在后台执行长期运行的任务并保持UI的响应是很重要的。async/await提供了一个干净的机制来执行异步任务。有的时候,一个方法在后台调用多个方法,默认情况下是按顺序进行这些调用。async 让其立即返回,允许代码进行下一个调用,然后所有返回的对象可以一起等待。这使得多个后台任务可以并行进行。

到此这篇关于在 Swift 中使用 async let 并发运行后台任务的文章就介绍到这了,更多相关Swift  async let 后台任务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Swift Package 技巧及混编兼容问题详解

    Swift Package 技巧及混编兼容问题详解

    这篇文章主要为大家介绍了Swift Package 技巧及混编兼容问题详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Swift进阶教程Mirror反射示例详解

    Swift进阶教程Mirror反射示例详解

    这篇文章主要为大家介绍了Swift进阶教程Mirror反射示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Swift 3.0将UILabel数字颜色设置为红色的方法

    Swift 3.0将UILabel数字颜色设置为红色的方法

    这篇文章主要介绍了关于在Swift中将UILabel数字颜色设置为红色的方法,文中给出了详细的示例代码,相信对大家具有一定的参考价值,需要的朋友们下面来一起看看吧。
    2017-03-03
  • Swift 并发修改Sendable 闭包实例详解

    Swift 并发修改Sendable 闭包实例详解

    这篇文章主要为大家介绍了Swift 并发修改Sendable 闭包实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • Swift 重构重载运算符示例解析

    Swift 重构重载运算符示例解析

    这篇文章主要为大家介绍了Swift 重构重载运算符示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • swift 错误处理do catch try try!使用详解

    swift 错误处理do catch try try!使用详解

    这篇文章主要介绍了swift 错误处理do catch try try!使用详解的相关资料,需要的朋友可以参考下
    2023-03-03
  • Swift中通知中心(NotificationCenter)的使用示例

    Swift中通知中心(NotificationCenter)的使用示例

    这篇文章主要给大家介绍了关于Swift中通知中心(NotificationCenter)使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2017-10-10
  • 简单理解插入排序算法及Swift版的代码示例

    简单理解插入排序算法及Swift版的代码示例

    插入排序算法可以在已排序的序列中将要插入的元素和原有元素保持有序,这里我们来简单理解插入排序算法及Swift版的代码示例,需要的朋友可以参考下
    2016-07-07
  • Swift心得笔记之集合类型

    Swift心得笔记之集合类型

    本文为大家讲解的是swift语言中的集合类型数据,这是swift开发必须掌握的知识点,感兴趣的同学参考下。
    2015-04-04
  • 详解Swift中的数据类型类型转换

    详解Swift中的数据类型类型转换

    Swift中的类型转换可以结合类的继承等面向对象的编程特性来进行,本文中我们就来详解Swift中的数据类型类型转换,需要的朋友可以参考下
    2016-07-07

最新评论