首页 > 其他分享 >大师学SwiftUI第9章Part 1 - 异步并发之Task、Async、Await和错误

大师学SwiftUI第9章Part 1 - 异步并发之Task、Async、Await和错误

时间:2023-11-14 15:07:10浏览次数:44  
标签:异步 Task name Await 任务 Part loadImage await

其它相关内容请见虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记

苹果系统借助现代处理器的多核可同步执行多条代码,提升同一时间内程序所能执行的任务。例如,一段代码从网上下载文件,另一段代码可以在屏幕上显示进度。此时,我们不能等待第一个执行完后再执行第二个,而必须要同步执行这两个任务。

要并行处理代码,系统将代码单元分组成任务。在Swift中,任务可以通过异步和并发编程实现。异步编程是一种编程模式,代码在完成任务前等待处理完成。这样系统可以在不同进程间共享计算资源。等待期间,系统可使用资源执行其它任务。而并发编程实现的代码可以利用多核同步执行多个任务。

大师学SwiftUI第9章Part 1 - 异步并发之Task、Async、Await和错误_SwiftUI

图9-1:异步和并发编程

因很多应用可以同时运行,系统并不会对每个应用分配指定的核数。系统会创建一些执行线程,将任务分配给这些线程,然后根据可用资源决定哪个核执行哪些线程。在图9-1的示例中,左边是一个异步任务,从网上加载图片然后在屏幕上显示。在等待服务响应时,线程处于空闲状态可以执行其它任务,因此系统可以使用它执行更新进度条的任务。右图中创建了并发任务,因此在不同进程中同步执行。

任务

异步和并发的代码由任务定义。Swift标准库中包含有Task结构体用于创建和管理这些任务。下面是结构体的初始化方法:

  • Task(priority: TaskPriority?, operation: Closure):这个初始化方法创建并运行新任务。priority参数是一个辅助系统决定何时执行任务的结构体。这一结构体中包含类型属性定义标准优先级。当前有backgroundhighlowmediumuserInitiatedutilityoperation参数是一个闭包,内含任务执行的语句。

Task结构带有如下属性用于取消任务。

  • isCancelled:该属性返回一个表示任务是否被取消的布尔值。
  • cancel():取消任务的方法。

还有一些类型属性和方法,可用于从当前任务获取信息或创建执行指定处理的任务。以下是最常用的。

  • currentPriority:该属性返回当前任务的优先级。这是一个TaskPriority结构体,有属性backgroundhighlowmediumuserInitiatedutility
  • isCancelled:该属性返回一个表示当前任务是否取消的布尔值。
  • sleep(nanoseconds: UInt64):本方法按照nanoseconds参数指定的时间挂起当前任务。

虽然可以在代码的任意地方创建Task结构体初始化异步任务,SwiftUI自带了如下的修饰符在视图出现时进行创建。

  • task(priority: TaskPriority, Closure):此修饰符在视图出现时执行第二个参数所指定的任务。priority参数是一个结构体,辅助系统决定何时执行任务。值有backgroundhighlowmediumuserInitiatedutility
  • task(id: Value, priority: TaskPriority, Closure):此修饰符在视图出现时执行第三个参数所指定的任务。id参数是用于标识任务的值。每当这个值发生改变时,任务就会重启。priority参数是一个结构体,辅助系统决定何时执行任务。值有backgroundhighlowmediumuserInitiatedutility

Async和Await

异步和并发任务在Swift中通过asyncawait关键字定义。例如要创建异步任务,我们使用async标注方法,然后使用await等待该方法执行完成。这表示在另一异步方法内只能通过await关键字调用一个异步方法,创建一个无限循环。开启这一循环,我们使用task()修饰符在视图出现时初始化异步任务,如下所示。

示例9-1:初始化异步任务

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
        }
        .task(priority: .background) {
            let imageName = await loadImage(name: "image1")
            print(imageName)
        }
    }
    func loadImage(name: String) async -> String {
        try? await Task.sleep(nanoseconds: 3 * 1000000000)
        return "Name: \(name)"
    }
}

本例中使用background优先级创建任务,表示它相对其它并行任务不具备优先级。在闭包中,我们调用loadImage()方法,然后在控制台中打印返回值。我们定义的这个方法模拟从网上下载图片。稍后我们会学习如何下载数据及连接网络,但这里我们使用了sleep()方法让任务暂停3秒,假装在下载图片(方法接收值的单位是纳秒)。停顿结束后,方法返回带文件名的字符串。要以异步定义方法,我们在参数的后面添加async关键字,然后使用await关键字调用它,表示任务必须等待处理完成。

task()修饰符创建任务并添加到线程中。在视图加载后,会执行赋值给修饰符的闭包。在闭包中,我们调用loadImage()方法,等待其完成。方法停顿3秒、返回字符串。此后,任务继续执行语句,在控制台上打印消息。

✍️跟我一起做:创建一个多平台项目。使用示例9-1中的代码更新ContentView结构体。在模拟器中运行应用。3秒后会看到控制中打印的消息。

一个任务可执行多个异步处理。例如,下例中调用了loadImage()3次来下载3张图片。

示例9-2:运行多异步处理

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
        }
        .task(priority: .background) {
            let imageName1 = await loadImage(name: "image1")
            let imageName2 = await loadImage(name: "image2")
            let imageName3 = await loadImage(name: "image3")
            print("\(imageName1), \(imageName2), \(imageName3)")
        }
    }
    func loadImage(name: String) async -> String {
        try? await Task.sleep(nanoseconds: 3 * 1000000000)
        return "Name: \(name)"
    }
}

这些处理逐条按顺序执行。任务会等待上一条处理结束再处理下一条。本例中,整个任务耗时9秒完成(每个处理3秒)。

✍️跟我一起做:使用示例9-2中的代码更新ContentView结构体。在模拟器中运行应用。9秒后会看到控制台中打印的消息。

只需在视图加载后运行异步任务使用task()修饰符很有用,但大多数时候任务和视图的生命周期无依赖关系,必须使用Task初始化方法显式地创建。比如可以通过onAppear()方法和Task结构体来重现上例。

示例9-3:显式定义任务

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
        }
        .onAppear {
            Task(priority: .background) {
                let imageName1 = await loadImage(name: "image1")
                let imageName2 = await loadImage(name: "image2")
                let imageName3 = await loadImage(name: "image3")
                print("\(imageName1), \(imageName2), \(imageName3)")
            }
        }
    }
    func loadImage(name: String) async -> String {
        try? await Task.sleep(nanoseconds: 3 * 1000000000)
        return "Name: \(name)"
    }
}

这一视图和之前一样执行了3条处理,但这里显式地定义了任务,我们有了更多的控制权。比如,现在可以将任务赋值给变量,然后调用cancel()方法取消任务。

cancel()方法用于取消任务,便处理不会自动取消,我们必须使用isCancelled属性监测任务是否被取消并自行停止任务,如下例所示。

示例9-4:取消任务

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
        }
        .onAppear {
            let myTask = Task(priority: .background) {
                let imageName = await loadImage(name: "image1")
                print(imageName)
            }
            Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) in
                print("The time is up")
                myTask.cancel()
            }
        }
    }
    func loadImage(name: String) async -> String {
        try? await Task.sleep(nanoseconds: 3 * 1000000000)
        if !Task.isCancelled {
            return "Name: \(name)"
        } else {
            return "Task Cancelled"
        }
    }
}

本例中将前面的任务赋值给了一个常量,然后创建一个定时器在2秒后调用任务的cancel()方法。在loadImage()方法中,我们读取isCancelled属性进行相对应的响应。如果取消了任务,返回Task Cancelled,否则和之前一样返回名称。注意本例我们是在任务执行的处理内操作,因此使用了类型属性,而不是实例属性(我们从数据类型而不是实例中读取isCancelled属性)。该属性根据当前任务的状态返回truefalse。任务在完成后就被取消了。

任务可接收并返回值。Task结构体包含一个value属性提供对任务返回值的访问。当然,我们需等待任务完成才能读取值,如下例如下。

示例9-5:读取任务的返回值

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
        }
        .onAppear {
            Task(priority: .background) {
                let imageName = await loadImage(name: "image1")
                print(imageName)
            }
        }
    }
    func loadImage(name: String) async -> String {
        let result = Task(priority: .background) { () -> String in
            let imageData = await getMetadata()
            return "Name: \(name) Size: \(imageData)"
        }
        let message = await result.value
        return message
    }
    func getMetadata() async -> Int {
        try? await Task.sleep(nanoseconds: 3 * 1000000000)
        return 50000
    }
}

因为我们需要等待任务完成才能使用值,所以定义了第二个任务。处理和之前一样启动,通过任务调用loadImage()方法,但现在创建了第二个返回字符串的任务。该任务执行另一个异步方法,等待3秒、返回数字50000。在处理结束后,任务使用名称和返回的数字创建字符串。然后通过value属性获取字符串,将其返回给原始任务,打印到控制台。

至此,我们使用了异步方法,但还可以定义异步属性。只需要使用async关键字定义getter。

示例9-6:定义异步属性

struct ContentView: View {
    var thumbnail: String {
        get async {
            try? await Task.sleep(nanoseconds: 3 * 1000000000)
            return "mythumbnail"
        }
    }
    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
        }
        .onAppear {
            Task(priority: .background) {
                let imageName = await thumbnail
                print(imageName)
            }
        }
    }
}

这次把调用方法改成由任务读取属性。属性在挂起任务3秒后返回字符串。这里做挂起只是为了进行演示,但在这个属性中可以执行任意需要的任务,比如处理或下载数据。

错误

异步任务不一定都能成功,所以必须准备好处理返回的错误。如果在创建自己的任务,可以通过实现Error协议的枚举来定义错误,在第3章中进行过讲解(见示例3-189)。下例中定义了一个含两个错误的结构体,一个在未找到服务端元数据(noData)时返回,另一个在图片不存在(noImage)时返回。

示例9-7:响应错误

enum MyErrors: Error {
    case noData, noImage
}
struct ContentView: View {
   
    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
        }
        .onAppear {
            Task(priority: .background) {
                do {
                    let imageName = try await loadImage(name: "image1")
                    print(imageName)
                } catch MyErrors.noData {
                    print("Error: No Data Available")
                } catch MyErrors.noImage {
                    print("Error: No Image Available")
                }
            }
        }
    }
    func loadImage(name: String) async throws -> String {
        try? await Task.sleep(nanoseconds: 3 * 1000000000)
        
        let error = true
        if error {
            throw MyErrors.noImage
        }
        return "Name: \(name)"
    }
}

上例中的loadImage()在测试代码时总是会抛出noImage错误。其中的任务通过do catch语句检测错误并在控制台打印消息报告错误。注意在异步方法可能会抛出错误时,必须在async后使用关键字throws进行声明。

标签:异步,Task,name,Await,任务,Part,loadImage,await
From: https://blog.51cto.com/u_1208143/8369394

相关文章

  • 即时通讯技术文集(第23期):IM安全相关文章(Part12) [共15篇]
    为了更好地分类阅读52im.net总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第23 期。[- 1 -] 理论联系实际:一套典型的IM通信协议设计详解(含安全层设计)[链接] http://www.52im.net/thread-283-1-1.html[摘要] 本文将以理论联系实际的方式,详细讲解一套典......
  • mysql-partition库-创建数据库表分区、展示表分区最新字段
    说明[tips]提供mysql数据库的分区表最新分区查询,支持分区字段为DATE或TO_DAYS(date_str)类型的数据表新建分区,依赖库包括:"pandas","pymysql","sqlalchemy".[ProvidethelatestpartitionqueryforthepartitiontableofMySQLdatabase,supportingpartitionfields......
  • Spartacus 服务器端渲染(SSR)的 timeout 设置
    如下图所示,SpartacusSSRengine的几种timeout超时机制的设置:其中第122行的3_000写法,意思就是默认的3000毫秒超时时间。在官网能看到对于这些timeout字段的说明:timeout的设置是一个数字,指示SSR服务器在回退到CSR默认的渲染机制之前,尝试呈现页面的时间量(以毫秒......
  • 编译 Spartacus 6.0 时遇到的错误消息
    错误消息如下:CompilingwithAngularsourcesinIvypartialcompilationmode.projects/storefrontlib/shared/components/generic-link/generic-link.component.html:22:6-errorTS2322:Type'string|null'isnotassignabletotype'string|undefine......
  • Spartacus CSR 渲染模式下的 home page 网络请求
    SpartacusCSR渲染模式下的homepage网络请求如下,其中第一个请求就是baseSitesOCCAPI调用。Spartacus是一个开源的、响应式的电子商务前端框架,是基于Angular构建的,旨在帮助企业快速构建现代化的电子商务网站。OCC(OmniCommerceConnect)是Spartacus与后端电子商务平台(通常......
  • 如何给 Spartacus 的 CSR 和 SSR 配置不同的 OCC endpoint
    SAP官方文档里,对CommerceCloudComposableStorefront的occendpoint配置说明的例子如下:provideConfig(backend:{occ:{baseUrl:'https://some.baseUrl.com'},},}),那么如果想为SSR和CSR两种运行方式,配置不同的o......
  • RLHF · PBRL | 发现部分 D4RL tasks 不适合做 offline reward learning 的 benchmark
    论文题目:BenchmarksandAlgorithmsforOfflinePreference-BasedRewardLearning,TMLR20230103发表。openreview:https://openreview.net/forum?id=TGuXXlbKsnpdf版本:https://arxiv.org/pdf/2301.01392.pdfhtml版本:https://ar5iv.labs.arxiv.org/html/2301.01392目......
  • 使用ES6生成器(Generators)和redux-saga与使用ES2017的async/await和redux-thunk相比的
    内容来自DOChttps://q.houxu6.top/?s=使用ES6生成器(Generators)和redux-saga与使用ES2017的async/await和redux-thunk相比的优缺点。目前关于redux的最新讨论焦点是redux-saga/redux-saga。它使用生成器函数来监听/分发actions。在我深入研究之前,我想了解使用redux-saga与下......
  • 关于.net async和await
    async/await异步编程不能提升响应速度,但是可以提升并发量.(比如100个并发请求,假设机器只能同时处理50个请求,用同步的话后面的50个要等待前面的完成才能执行,而使用异步可能可以把机器同时处理50个请求提升到70+,因为线程会回收利用,不会一直等待任务完成,因此可以提高并发量)......
  • SharePoint 的 Web Parts 是什么
    WebParts可以说是微软SharePoint的基础组件。根据微软自己的描述,WebParts是SharePoint对内容进行构建的基础,可以想想成一块一块的砖块。我们需要使用这些砖块来完成一个页面的构建。我们可以利用WebParts在SharePoint中添加文本,图片,文件,视频,甚至是动态内容。添加Web......