Swift 并发中的任务让步(Yielding)和防抖(Debouncing)

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- 什么是任务防抖(Debouncing)?
- 如何用 sleep 实现防抖?
- 什么是任务让步(Task Yielding)?
- 如何用 yield 让出线程?
- 什么时候需要 yield?
- 总结
前言
本篇文章的主题是 任务让步(Task Yielding) 和 防抖(Debouncing)。Swift 并发为我们提供了两个简单但非常强大的函数:yield 和 sleep。今天我们就来看看它们的用法,以及在什么场景下应该使用它们。
什么是任务防抖(Debouncing)?
想象一下,你正在开发一个搜索功能,用户每输入一个字符,程序就会去一个庞大的数据集里查找匹配的结果。如果不加控制,每次键入都会触发新的搜索任务,可能会导致多个任务同时执行,影响性能甚至引发竞争条件(Race Condition)。
比如,下面这个 SwiftUI 代码:
@MainActor @Observable final class Store {private(set) var results: [HKCorrelation] = []private let store = HKHealthStore()func search(matching query: String) async {// 执行复杂的搜索任务}
}struct ContentView: View {@State private var store = Store()@State private var query = ""var body: some View {NavigationStack {List(store.results, id: \.uuid) { result inText(verbatim: result.endDate.formatted())}.searchable(text: $query).task(id: query) {await store.search(matching: query)}}}
}
在这个代码里,每次用户输入新字符,都会创建一个新的任务去执行搜索。而 Swift 并发采用 协作式取消机制(Cooperative Cancellation),也就是说,它不会直接强行终止任务,而是提供一个“取消标记”,任务需要自己检查并响应取消请求。因此,这种写法可能会导致多个搜索任务并行运行,消耗不必要的计算资源。
为了解决这个问题,我们可以用 防抖(Debouncing) 技术。
如何用 sleep 实现防抖?
防抖的思路很简单:
- 用户输入时,我们 等待一小段时间,看看用户是否继续输入。
- 如果输入仍然在变化,我们就 继续等待,而不是立即启动搜索。
- 只有当输入 稳定 一定时间后,才触发搜索任务。
换句话说,如果用户输入 "apple",我们希望忽略 "a", "ap", "app", "appl",只在最终输入 "apple" 后再进行搜索。
在 Swift 并发中,我们可以用 Task.sleep 来实现这个效果:
struct ContentView: View {@State private var store = Store()@State private var query = ""var body: some View {NavigationStack {List(store.results, id: \.uuid) { result inText(verbatim: result.endDate.formatted())}.searchable(text: $query).task(id: query) {do {try await Task.sleep(for: .seconds(1)) // 等待 1 秒await store.search(matching: query) // 执行搜索} catch {// 任务可能因为新输入被取消}}}}
}
为什么这样就能实现防抖?
Task.sleep(for: .seconds(1))让当前任务暂停 1 秒。- 如果用户在 1 秒内继续输入,之前的任务会被取消,新任务重新计时。
- 只有 用户停止输入超过 1 秒,才会触发真正的搜索任务。
效果: 这样可以避免在输入过程中反复触发搜索,减少不必要的计算量。
什么是任务让步(Task Yielding)?
除了防抖,Swift 并发还提供了 任务让步(Task Yielding),让你在执行长时间任务时,主动把线程让出来,让其他任务有机会运行。
想象一个场景:
你需要解析一批巨大的 JSON 文件,并将数据保存到磁盘。这个过程可能会运行很久,占用线程资源。如果你在主线程或并发线程池(Cooperative Thread Pool)上运行这种任务,会 阻塞其他任务的执行,导致性能问题。
比如,下面是一个解析 JSON 文件的代码:
struct Item: Decodable {// 解析 JSON 的结构
}struct DataHandler {func process(json files: [Data]) async throws -> [Item] {let decoder = JSONDecoder()var result: [Item] = []for file in files {let items = try decoder.decode([Item].self, from: file)result.append(contentsOf: items)}return result}
}
这个 process 函数会遍历所有 JSON 文件,并解析它们。但问题是:
- 解析 JSON 是一个 同步操作,它不会自动释放 CPU 资源。
- 如果 JSON 文件很大,整个解析过程可能会 占用线程很长时间,导致其他任务被阻塞。
如何用 yield 让出线程?
为了解决这个问题,我们可以 在每次解析完一个 JSON 文件后,让出线程,让其他任务有机会执行:
struct DataHandler {func process(json files: [Data]) async throws -> [Item] {let decoder = JSONDecoder()var result: [Item] = []for file in files {let items = try decoder.decode([Item].self, from: file)result.append(contentsOf: items)await Task.yield() // 让出线程,让其他任务有机会执行}return result}
}
任务让步的好处:
await Task.yield()会让当前任务 暂停一下,让其他等待中的任务有机会执行。- 之后,系统会恢复这个任务的执行,继续处理下一个 JSON 文件。
- 这样可以 更公平地分配 CPU 资源,防止某个任务独占线程。
什么时候需要 yield?
通常来说,如果你的代码已经是 异步的(async/await),系统会自动在 await 语句处让出线程。所以 大部分情况下,你不需要手动 yield。
但是,当你处理 非异步 API(比如 JSON 解析、图片处理、大量计算等)时,手动 yield 可能会提升性能。
总结
-
防抖(Debouncing)
- 适用于 用户频繁输入的场景,如搜索框、按钮点击等。
- 通过
Task.sleep(for:)实现,等输入稳定后再执行任务。 - 避免频繁创建任务,提高性能。
-
任务让步(Task Yielding)
- 适用于 长时间运行的计算密集型任务,如解析 JSON、图片处理等。
- 通过
Task.yield()让出 CPU,避免线程被长时间占用。 - 让其他任务有机会执行,提高系统响应速度。
这两个技巧虽然简单,但在实际开发中非常有用,可以帮助你更高效地利用 Swift 并发,让你的应用运行得更流畅!
相关文章:
Swift 并发中的任务让步(Yielding)和防抖(Debouncing)
网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…...
@SpringBootApplication
SpringBootApplication拓展 一. SpringBootConfiguration注解 是SpringBoot的注解, 标识一个类为配置类, 与Configration功能一致 run方法初始化了SpringBootConfiguration注解 注解源码 Target(ElementType.TYPE)//类型 Retention(RetentionPolicy.RUNTIME)//生命周期 Docu…...
什么是状态管理?有何种方式可以实现?它们之间有什么区别?
目录 一、状态管理的核心概念 二、常见状态管理方案及对比 1. 基础方案:setState 2. 官方推荐:Provider 3. 事件驱动:Bloc (Business Logic Component) 4. 响应式增强:Riverpod 5. 轻量级全能库:GetX 三、方案对比与选型指南 四、实战建议 在 Flutter 中,状态管…...
HW基本的sql流量分析和wireshark 的基本使用
前言 HW初级的主要任务就是看监控(流量) 这个时候就需要我们 了解各种漏洞流量数据包的信息 还有就是我们守护的是内网环境 所以很多的攻击都是 sql注入 和 webshell上传 (我们不管对面是怎么拿到网站的最高权限的 我们是需要指出它是…...
docker-compose install nginx(解决fastgpt跨区域)
CORS前言 CORS(Cross-Origin Resource Sharing,跨源资源共享)是一种安全措施,它允许或拒绝来自不同源(协议、域名、端口任一不同即为不同源)的网页访问另一源中的资源。它的主要作用如下: 同源策略限制:Web 浏览器的同源策略限制了从一个源加载的文档或脚本如何与另一…...
设计模式(创建型)-单例模式
摘要 在软件开发的世界里,设计模式是开发者们智慧的结晶,它们为解决常见问题提供了经过验证的通用方案。单例模式作为一种基础且常用的设计模式,在许多场景中发挥着关键作用。本文将深入探讨单例模式的定义、实现方式、应用场景以及可…...
Leetcode 刷题笔记1 图论part01
图论的基础知识: 图的种类: 有向图(边有方向) 、 无向图(边无方向)、加权有向图(边有方向和权值) 度: 无向图中几条边连接该节点,该节点就有几度࿱…...
鸿蒙NEXT开发问题大全(不断更新中.....)
目录 问题1:鸿蒙NEXT获取华为手机的udid 问题2:[Fail]ExecuteCommand need connect-key? 问题3:测试时如何安装app包 问题1:鸿蒙NEXT开发获取华为手机的udid hdc -t "设备的序列号" shell bm get --udid 问题2&…...
分享一个项目中遇到的一个算法题
需求背景: 需求是用户要创建一个任务计划在未来执行,要求在创建任务计划的时候判断选择的时间是否符合要求,否则不允许创建,创建的任务类型有两种,一种是单次,任务只执行一次;另一种是周期&…...
TI的Doppler-Azimuth架构(TI文档)
TI在AWR2944平台上推出新的算法架构,原先的处理方式是做完二维FFT后在RD图上做CFAR检测,然后提取各个通道数据做测角。 Doppler-Azimuth架构则是做完二维FFT后,再做角度维FFT,生成Doppler-Azimuth频谱图,然后在该频谱图…...
电子邮件常用协议技术详解与C++实践(SMTP POP3 IMAP)
一、核心协议概览 协议端口(明文/加密)核心功能数据同步方式典型场景SMTP25 / 587邮件发送单向传输客户端提交邮件POP3110 / 995邮件下载单向同步单设备离线阅读IMAP143 / 993邮件管理双向同步多设备实时同步 二、协议深度解析 1. SMTP(简单…...
机器学习算法:一文掌握 K近邻算法 的详细用法(2个案例可直接运行)
文章目录 一、KNN 算法概述1.1 算法原理1.2 KNN 的优缺点1.3 K 值的选择 二、Python 实现 KNN 案例2.1 使用 KNN 算法进行手写数字识别2.2 使用 Python 实现 KNN 分类 三、总结 KNN(K-Nearest Neighbors,K近邻算法) 是一种简单且常用的分类和…...
设计C语言的单片机接口
一、主要内容 (一)控制引脚 1、定义管脚 // 定义管脚的结构体 struct pin{ int id; // 管脚编号 int mode; // 模式,输入为1,输出为0 int pull; // 输入电阻 int driver; // 功率 } 2、输出电平 语法: void pin_output(s…...
[从零开始学习JAVA] Stream流
前言: 本文我们将学习Stream流,他就像流水线一样,可以对我们要处理的对象进行逐步处理,最终达到我们想要的效果,是JAVA中的一大好帮手,值得我们了解和掌握。(通常和lambda 匿名内部类 方法引用相…...
「自动驾驶的数学交响曲:线性代数、微积分与优化理论的深度共舞」—— 解析人工智能背后的高阶数学工具链
引言 自动驾驶系统是数学工具链的集大成者。从传感器数据的多维空间映射到控制指令的生成,每一步都隐藏着线性代数、微积分、概率论和优化理论的精妙配合。本文将构建一个数学模型完整的自动驾驶案例,结合Python代码实现,揭示以下核心数学工具: 线性代数:张量运算与特征空…...
调试 Rust + WebAssembly 版康威生命游戏
1. 启用 Panic 日志 1.1 让 Panic 信息显示在浏览器控制台 如果 Rust 代码发生 panic!(),默认情况下不会在浏览器开发者工具中显示详细的错误信息。这使得排查问题变得困难。 我们可以使用 console_error_panic_hook 这个 Rust crate,将 Panic 信息打…...
VSCode通过SSH远程登录Windows服务器
系列 1.1 VSCode通过SSH远程登录Windows服务器 1.2 VSCode通过SSH免密远程登录Windows服务器 文章目录 系列1 准备工作2 远程服务器配置2.1 安装SSH服务器2.2 端口 3 本地电脑配置3.1 安装【Remote - SSH】。3.2 登录 1 准备工作 本地电脑Windows 11,已安装VS Cod…...
qt下载和安装教程国内源下载地址
qt不断在更新中,目前qt6日渐成熟,先前我们到官方下载或者国内镜像直接可以下载到exe文件安装,但是最近几年qt官方似乎在逐渐关闭旧版本下载通道,列为不推荐下载。但是qt5以其广泛使用和稳定性,以及积累大量代码使得qt5…...
使用htool工具导出和导入Excel表
htool官网 代码中用到的hool包里面的excel工具ExcelUtil 1. 引入依赖 <!-- Java的工具类 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version></dependency>&l…...
mysql 到 doris 挪移数据
工具datax..... 下载地址:http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz 下载以后解压:tar -xvzf datax.tar.gz 然后,理论上就可以直接使用了。但是,datax本身是python2写的,如果需要python3…...
Springboot中的@ConditionalOnBean注解:使用指南与最佳实践
在使用Spring Boot进行开发时,大家应该都听说过条件注解(Conditional Annotations)。其中的ConditionalOnBean注解就很有趣,它帮助开发者在特定条件下创建和注入Bean,让你的应用更加灵活。今天就来聊聊这个注解的使用场…...
ubuntu系统下添加pycharm到快捷启动栏方法
一、背景 之前在ubuntu系统下使用pycharm时,总是要进入/home/dlut/pycharm-community-2022.1/bin文件夹下,然后终端执行命令下面的命令才可修改代码: ./pycharm.sh为了以后方便,这里给出添加pycharm到快捷启动栏的方法 二、添加…...
开源:LMDB 操作工具:lmcmd
目录 什么是 LMDB为什么编写 lmcmd安装方法如何使用 连接数据库命令列表 小结 1. 什么是 LMDB LMDB(Lightning Memory-Mapped Database)是一种高效的键值存储数据库,基于内存映射(memory-mapping)技术,提供…...
阿里云底层使用的虚拟化技术
阿里云底层使用的虚拟化技术主要是KVM([Kernel-based Virtual Machine)。KVM是一种基于内核的虚拟机技术,它允许Linux内核直接管理虚拟机的创建和运行,提供高效的虚拟化解决方案12。 KVM技术的特点和应用场景 KVM具有以下…...
angular中的路由传参
目录 一、矩阵参数 一、矩阵参数 在angular中传参时可以使用矩阵参数,即直接通过变量值的形式在地址中体现,但需要注意参数的使用范围为当前路径段,而不是全局的查询参数。 const params {name: lhhh,age: 18,list: [{ name: htt }],}; //先…...
AI时代下的心理咨询师新利器:心理咨询小程序
在AI技术日新月异的今天,心理咨询师们也需要与时俱进,借助新型工具来提升咨询效率和服务质量。正如一位优秀的厨师离不开一把锋利的菜刀,心理咨询师同样需要一款得力助手来辅助其工作。而心理咨询小程序,正是这样一款应运而生的工…...
垃圾分类--环境配置
写在前面: 如果你们打这届比赛时,还有我们所保留的内存卡,那么插上即可运行(因为内存卡里我们已经配置好所有的环境) 本文提供两种环境的配置 一种是基于yolov8:YOLOv8 - Ultralytics YOLO Docshttps://d…...
每日一题--计算机网络
一、基础概念类问题 1. TCP 和 UDP 的区别是什么? 回答示例: TCP:面向连接、可靠传输(通过三次握手建立连接,丢包重传)、保证数据顺序(如文件传输、网页访问)。 UDP:无…...
json字符串转对象,对象转JSON
背景: JSON字符串与对象之间的转换。在对接接口的数据的时候,因为是实时数据转发过来的。发现后端发过的数据是字符串【JSON字符串】但是我们前端需要的是一个对象。 核心代码: JSON.parse(JSON字符串) 效果展示: 接口JSON字符串转…...
c++ 基础题目lambda
1. auto lambda = [](double x) { return static_cast<int>(x); }; 是 匿名函数对象 ,不可直接声明 a.可以赋值给一个与其类型兼容的 std::function 类型的对象 std::function<int(int, int)> lambda = [](int x, int y) { return x + y; }; b.使用具体的 lambda …...
