Swift concurrency 4 — Task和.task的理解与使用

news2024/9/24 21:18:02

Task

Swift中的Task是一种异步操作,它提供了一种替代DispatchQueue.main.async{}等传统方法的方法。通过使用Task,我们可以简化代码并更好地控制异步操作。此外,Task还提供了其他选项,可以进一步增强任务执行。

先看一个Task的基本使用:

class TaskViewModel: ObservableObject {
    @Published var image: UIImage? = nil
    
    func fetchImage() async {
        do {
            guard let url = URL(string: "https://picsum.photos/1000") else { return }
            let (data, _) = try await URLSession.shared.data(from: url)
            await MainActor.run {
                self.image = UIImage(data: data)
                print("---> 图片请求成功!")
            }
        } catch {
            print("\(error.localizedDescription)")
        }
    }
}

struct TaskViewDemo: View {
    @StateObject private var viewModel = TaskViewModel()
    var body: some View {
        VStack {
            if let image = viewModel.image {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
            }
        }
        .onAppear {
            Task {
                await viewModel.fetchImage()
            }
        }
    }
}

上面代码中我们在TaskViewModel中请求一张图片,然后在View中显示,请求是在onAppear中使用了Task,前面的文章中也早就涉及过。

现在如果再请求一张图片,在同一个Task中请求两个图片,比如下面的代码:

class TaskViewModel: ObservableObject {
    @Published var image: UIImage? = nil
    @Published var image2: UIImage? = nil
    
    func fetchImage() async {
        do {
            guard let url = URL(string: "https://picsum.photos/1000") else { return }
            let (data, _) = try await URLSession.shared.data(from: url)
            await MainActor.run {
                self.image = UIImage(data: data)
                print("---> 图片请求成功!")
            }
        } catch {
            print("\(error.localizedDescription)")
        }
    }
    
    func fetchImage2() async {
        do {
            guard let url = URL(string: "https://picsum.photos/1000") else { return }
            let (data, _) = try await URLSession.shared.data(from: url)
            await MainActor.run {
                self.image2 = UIImage(data: data)
                print("---> 图片请求成功!")
            }
        } catch {
            print("\(error.localizedDescription)")
        }
    }
}

struct TaskViewDemo: View {
    @StateObject private var viewModel = TaskViewModel()
    var body: some View {
        VStack {
            if let image = viewModel.image {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
            }
            if let image = viewModel.image2 {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
            }
        }
        .onAppear {
            Task {
                await viewModel.fetchImage()
                await viewModel.fetchImage2()
            }
        }
    }
}

在同一个Task里面又添加了一个图片的请求,那么执行顺序是怎么样的呢?相比大家也猜到了,第二个会在第一个请求回来后再去请求,毕竟有await在那呢。
请添加图片描述
现在对代码做一下改动,分别用两个Task去请求两个图片,调用如下:

.onAppear {
    Task {
        await viewModel.fetchImage()
    }
    Task {
        await viewModel.fetchImage2()
    }
}

请添加图片描述
从效果上看,两个Task之间是不存在相互依赖的关系的,两张图片几乎是同一时间请求回来的。

TaskPriority

SwiftUI 中,Task(priority:operation:) 是用于创建并启动异步任务的一种方式,其中 TaskPriority 参数用于指定任务的优先级。优先级决定了系统在调度和执行任务时如何分配资源,特别是在多任务执行时,优先级较高的任务可能会优先获得执行机会。

TaskPriority 是一个枚举类型,定义了任务的优先级。它包含以下几种常见的优先级选项:

  • .high:高优先级任务,通常用于需要立即响应的任务。例如,用户界面的更新任务。
  • .medium:中等优先级任务,适用于一般任务。默认情况下,许多任务会使用此优先级。
  • .low:低优先级任务,适用于可以延迟执行的任务。例如,后台数据同步或预加载任务。
  • .background:后台任务优先级,用于那些不需要用户立即注意的任务。适用于后台处理或预加载数据。
  • .userInitiated:用户发起的任务优先级,通常用于用户直接触发的任务,比如用户按下按钮后启动的操作。优先级略高于 .medium
  • .utility:实用任务优先级,适用于需要一定时间完成,但不需要立即响应的任务。比如下载数据或处理文件。

在这里插入图片描述
上面的图片中,我们用代码将每个优先级都打印了一下,通过数字可以更好的看出优先级顺序。
另外这里还要强调一下,不是说高优先级的一定会最先去执行,最终的调度仍然由系统管理。系统会根据当前的负载、资源可用性等因素综合决定任务的执行顺序。

在一个Task中再调用一个Task,那么子Task的优先级会继承父Task的优先级。
在这里插入图片描述
当不指定优先级的时候,默认优先级是high。现在我们指定一个并在内部再添加一个Task看看。
在这里插入图片描述
如果子Task未指定优先级,那么就会继承父一级的优先级。如果指定了就不继承了,比如下图:
在这里插入图片描述
如果子Task又不想继承父Task优先级,又不想指定优先级,那么可以用

Task.detached {
    print("child priority : \(Task.currentPriority)")
}

效果如下:
在这里插入图片描述
不过关于Task.detached方法,苹果也有特别的说明,如下:

/// Don’t use a detached task if it’s possible
/// to model the operation using structured concurrency features like child tasks.
/// Child tasks inherit the parent task’s priority and task-local storage,
/// and canceling a parent task automatically cancels all of its child tasks.
/// You need to handle these considerations manually with a detached task.
/// You need to keep a reference to the detached task
/// if you want to cancel it by calling the Task.cancel() method.
/// Discarding your reference to a detached task
/// doesn’t implicitly cancel that task,
/// it only makes it impossible for you to explicitly cancel the task.

从第一句话就可以看出苹果是不推荐用这个Task.detached方法的。

cancel(取消任务)

Task.cancel()Task 中的一个方法,用于请求取消正在运行的任务。了解如何使用 Task.cancel() 非常重要,尤其是在处理长时间运行的异步操作时,确保可以在需要时安全地停止这些操作。
Task.cancel() 是一个实例方法,用于标记任务为取消状态。当你调用 cancel() 方法时,你实际上是在通知任务:“你应该尽快停止工作。” 但是需要注意的是,调用 cancel() 并不会立即终止任务,而是设置一个取消标志(isCancelled 属性变为 true),然后任务内部代码需要定期检查这个标志,并自行决定是否中止任务。

如何正确使用 Task.cancel()

  1. 启动任务:
    可以使用 Task { ... } 来启动一个异步任务。
  2. 请求取消:
    当需要取消任务时,可以调用 cancel() 方法。
  3. 任务响应取消:
    任务内部应定期检查 Task.isCancelled 属性,确保在任务被取消时能够及时响应,并终止正在执行的操作。

还是接着上面的代码说,我们将上面的代码放到导航栏的第二页,由主页面push出来,到第二个界面后请求图片的时候加上5秒延迟,并在onDisappear方法中调用cancel()方法,然后未到5秒的时候返回到第一个界面。代码如下:

func fetchImage() async {
    try? await Task.sleep(nanoseconds: 5_000_000_000)
    do {
        guard let url = URL(string: "https://picsum.photos/1000") else { return }
        let (data, _) = try await URLSession.shared.data(from: url)
        await MainActor.run {
            self.image = UIImage(data: data)
            print("---> 图片请求成功!")
        }
    } catch {
        print("\(error.localizedDescription)")
    }
}

上面代码添加了5秒的延迟。

struct TaskViewDemo: View {
    @StateObject private var viewModel = TaskViewModel()
    
    @State private var fetchImageTask: Task<(), Never>? = nil
    
    var body: some View {
        VStack {
            if let image = viewModel.image {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
            }
        }
        .onAppear {
            fetchImageTask = Task {
                await viewModel.fetchImage()
            }
        }
        .onDisappear {
            fetchImageTask?.cancel()
        }
    }
}

上面代码中定义了一个fetchImageTask实例变量,用来持有Task实例,并在onDisappear方法调用cancel()方法。
请添加图片描述
很明显,在未到5秒的时候返回,控制台能看到系统的已取消输出。
但是实际开发中很少有这种等几秒的情况,如果在ViewModel中有循环处理事件,那么就要判断一下当前的任务是否已经取消了

Task.checkCancellation()

调用checkCancellation()方法后,如果任务中途取消了,那么该方法将抛出异常,代码块也不会再继续执行了。调用时如果不想处理异常错误,直接使用try?即可:

try? Task.checkCancellation()

简单示例如下:

for work in workArray {
    // 检查是否已经取消
    try? Task.checkCancellation()
    // 任务代码
    // ......
}

还要说一下,定义Task实例的时候,因为Task是泛型,需要指定SuccessFailure

@frozen public struct Task<Success, Failure> : Sendable where Success : Sendable, Failure : Error {
}

.task(SwiftUI)

.task 修饰符用于在视图的生命周期中启动一个异步任务。这些任务会在视图出现时自动启动,并且可以与 Swift 的并发功能一起使用,如 asyncawait

.task(priority: TaskPriority? = nil, _ action: @escaping () async -> Void) -> some View
  • priority:可选的任务优先级。可以是 .low, .medium, .high, .userInitiated,.background 等。默认为 nil,表示使用系统默认优先级。
  • action:在视图出现时执行的异步代码块。

使用场景

  • 视图加载时初始化数据:在视图加载时,使用 .task 修饰符来启动异步数据加载或初始化任务。
  • 执行后台操作:启动一些不影响 UI 但需要后台处理的任务,例如分析数据、同步远程服务器等。

比如刚才的示例中:

.task {
    await viewModel.fetchImage()
}

.task 修饰符使得在 SwiftUI 中处理异步任务变得更加简洁和直观,特别是在与 Swift 的并发功能结合使用时,能够轻松处理复杂的异步操作。

注意事项

  • 任务取消:.task 启动的任务会在视图被移除时自动取消。因此,如果视图在任务完成之前被移除,该任务将不会继续运行。
  • .onAppear 的区别:.task 类似于 .onAppear,但 .task 专门用于启动异步任务,并且能够更好地处理 Swift并发模型。.onAppear 通常用于启动同步代码或不需要异步处理的逻辑。
  • 任务优先级:可以使用 priority 参数来设置任务的优先级,但这通常在需要优化性能或资源分配时才需要考虑。

小结

本篇文章主要介绍了Task的基本使用,优先级,以及取消等功能,在SwiftUI中也可以使用.task修饰符来代替Task,而且视图销毁的时候还能自动cancel任务。关于Task.task的使用,想必大家通过这篇文章也有所了解了。感谢大家的阅读。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2084201.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

net core中byte数组如何高效转换为16进制字符串

在 .NET Core 中&#xff0c;如何把 byte[] 转换为 16 进制字符串&#xff1f;你能想到哪些方法&#xff1f;什么方式性能最好&#xff1f;今天和大家分享几种转换方式。 往往在处理字符串性能问题时&#xff0c;首先应该想到的是怎么想办法减少内存分配&#xff0c;怎么优化字…

22.优化器

优化器 当使用损失函数时&#xff0c;可以调用损失函数的 backward&#xff0c;得到反向传播&#xff0c;反向传播可以求出每个需要调节的参数对应的梯度&#xff0c;有了梯度就可以利用优化器&#xff0c;优化器根据梯度对参数进行调整&#xff0c;以达到整体误差降低的目的。…

Cryptomator:开源云存储加密

采用最新技术标准&#xff0c;提供最佳保护 如果有人查看您云中的文件夹&#xff0c;他们无法对您的数据得出任何结论。 Cryptomator 提供开源的客户端云文件加密。 它适用于 Windows、Linux、macOS 和 iOS。 Cryptomator 可与 Dropbox、Google Drive、OneDrive、MEGA、pClo…

【QT | 开发环境搭建】Linux系统(Ubuntu 18.04) 安装 QT 5.12.12 开发环境

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 2024-08-29 …

C# 委托详解(Delegate)

引言 在 C# 编程当中&#xff0c;委托&#xff08;Delegate&#xff09;是一种特殊的类型&#xff0c;它允许将方法作为参数传递给其他方法&#xff0c;或者将方法作为返回值返回&#xff0c;这种特性使得委托成为实现回调函数、事件处理等&#xff0c;所有的委托都派生自Syst…

【STM32开发笔记】使用RT-Thread的SDIO驱动和FATFS实现SD卡文件读写

【STM32开发笔记】使用RT-Thread的SDIO驱动和FATFS实现SD卡文件读写 一、准备工作1.1 准备好开发板和SD卡1.2 创建RT-Thread项目 二、配置RT-Thread2.1 打开文件系统相关配置2.2 打开SD卡相关配置2.3 打开RTC配置2.4 重新生成Keil项目文件 三、编译、烧录、运行3.1 编译项目3.2…

网站建设完成后, 营销型网站如何做seo

营销型网站的SEO优化旨在提高网站在搜索引擎中的排名&#xff0c;从而吸引更多潜在客户并促进销售。以下是营销型网站SEO的详细解析&#xff1a; 关键词研究与优化 目标受众分析&#xff1a;了解目标受众的搜索习惯和需求&#xff0c;确定适合的关键词。使用工具来发现相关关键…

RV1126的GPIO计算和使用

1、获取GPIO芯片对应的序号值 先读取下/sys/kernel/debug/gpio的值&#xff0c;得到每个GPIO芯片的序号范围&#xff0c;如GPIO芯片0就为0~31。 2、根据GPIO硬件编号计算出系统内使用的GPIO序号 根据GPIO的编号&#xff0c;比如说GPIO3_B0&#xff0c;前面GPIO3代表看GPIO3的信…

傻瓜操作:GraphRAG、Ollama 本地部署及踩坑记录

目录 一、GraphRAG 介绍1.引言2.创新点3. 算法4. 数据和实验结果5.不足和展望 二、本地部署1.为什么要本地部署2.环境准备3. GraphRAG 安装3.1 下载 GraphGAG3.2 安装依赖包3.3 创建数据目录3.4 项目初始化3.5 修改配置文件 3.6 修改.env文件3.7 修改源码 4. Indexing5. query5…

Linux关于压缩之后文件更大的解释

记录于24年八月29 使用vim命令创建了lianxi1和lianxi2并在里面填写了一些内容&#xff0c;发现使用gzip和zip压缩后文件反而更大 事后问了一下ai回答了我的疑惑 压缩算法开销&#xff1a;如前所述&#xff0c;压缩文件需要存储额外的元数据和文件结构信息。这种开销在处理非常…

C++ TinyWebServer项目总结(13. 多进程编程)

本章讨论Linux多进程编程的以下内容&#xff1a; 复制进程映像的fork系统调用和替换进程映像的exec系列系统调用。僵尸进程以及如何避免僵尸进程。进程间通信&#xff08;Inter Process Communication&#xff0c;IPC&#xff09;最简单的方式&#xff1a;管道。三种System V进…

浏览器插件利器--allWebPluginV2.0.0.18-alpha版发布

allWebPlugin简介 allWebPlugin中间件是一款为用户提供安全、可靠、便捷的浏览器插件服务的中间件产品&#xff0c;致力于将浏览器插件重新应用到所有浏览器。它将现有ActiveX控件直接嵌入浏览器&#xff0c;实现插件加载、界面显示、接口调用、事件回调等。支持Chrome、Firefo…

[MRCTF2020]Unravel!!

使用zsteg查看图片有隐藏文件&#xff0c;没有头绪&#xff0c;先放弃 使用zsteg和010editor查看都发现一个png图片 把JM.png拷贝到kali&#xff0c;使用binwalk分离&#xff0c;得到一个aes.png 使用010editor查看wav&#xff0c;发现尾部有可疑的字符串&#xff0c;拷贝出来备…

记一次应急响应之网站暗链排查

目录 前言 1. 从暗链而起的开端 1.1 暗链的介绍 1.2 暗链的分类 2. 在没有日志的情况下如何分析入侵 2.1 寻找指纹 2.2 搜索引擎搜索fofa资产搜索 2.2.1 fofa资产搜索 2.2.2 bing搜索引擎搜索 3.通过搭建系统并进行漏洞复现 4. 应急响应报告编写 前言 免责声明 博文…

二叉树(binary tree)遍历详解

一、简介 二叉树常见的遍历方式包括前序遍历、中序遍历、后序遍历和层序遍历等。我将以下述二叉树来讲解这几种遍历算法。 1、创建二叉树代码实现 class TreeNode:def __init__(self,data):self.datadataself.leftNoneself.rightNonedef createTree():treeRootTreeNode(F)N…

大模型提示词工程技术3-提示词输入与输出的优化的技巧详细介绍

大模型提示词工程技术3-提示词输入与输出的优化的技巧详细介绍。《大模型提示词工程技术》的作者&#xff1a;微学AI&#xff0c;这是一本专注于提升人工智能大模型性能的著作&#xff0c;它深入浅出地讲解了如何通过优化输入提示词来引导大模型生成高质量、准确的输出。书中不…

腾讯地图三维模型加载GLTF,播放模型动画

腾讯地图三维模型加载&#xff0c;播放模型动画 关键代码 const clock new THREE.Clock();console.log(gltf)// 确保gltf对象包含scene和animations属性if (gltf && gltf.scene && gltf.animations) {// 创建AnimationMixer实例&#xff0c;传入模型的scenec…

【51单片机】2-3-1 【I/O口】【电动车防盗报警项目】震动传感器实验1—震动点灯

1.硬件 51单片机最小系统LED灯模块震动传感器模块 2.软件 main.c程序 #include "reg52.h"sbit led1 P3^7;//根据原理图&#xff08;电路图&#xff09;&#xff0c;设备变量led1指向P3组IO口的第7口 sbit vibrate P3^3;//Do接到了P3.3口void Delay2000ms() //…

力扣刷题--2185. 统计包含给定前缀的字符串【简单】

题目描述 给你一个字符串数组 words 和一个字符串 pref 。 返回 words 中以 pref 作为 前缀 的字符串的数目。 字符串 s 的 前缀 就是 s 的任一前导连续字符串。 示例 1&#xff1a; 输入&#xff1a;words [“pay”,“attention”,“practice”,“attend”], pref “at…

用 Higress AI 网关降低 AI 调用成本 - 阿里云天池云原生编程挑战赛参赛攻略

作者介绍&#xff1a;杨贝宁&#xff0c;爱丁堡大学博士在读&#xff0c;研究方向为向量数据库 《Higress AI 网关挑战赛》正在火热进行中&#xff0c;Higress 社区邀请了目前位于排行榜 top5 的选手杨贝宁同学分享他的心得。下面是他整理的参赛攻略&#xff1a; 背景 我们…