文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-08-17 07:46

关注

前言

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.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()    }}

模拟同步下载一个文件--没有实时更新UI

使用 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..            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()    }}

使用 async/await 来模拟下载一个文件,同时更新UI

在后台执行多个任务

现在我们有一个文件在后台下载,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.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)        ]    }}

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 await来模拟按顺序下载多个文件

使用 "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.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)        ]    }}

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 让其立即返回,允许代码进行下一个调用,然后所有返回的对象可以一起等待。这使得多个后台任务可以并行进行。

来源地址:https://blog.csdn.net/qq_36478920/article/details/131371849

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯