如何让异步序列(AsyncSequence)优雅的感知被取消(Cancel)

news2024/11/25 1:04:52

在这里插入图片描述

概览

自  从 Swift 5.5 推出新的 async/await 并发模型以来,异步队列(AsyncSequence)就成为其中不可或缺的重要一员。

不同于普通的序列,异步序列有着特殊的“惰性”和并发性,若序列中的元素还未准备好,系统在耐心等待的同时,还将宝贵的线程资源供其它任务去使用,极大的提高了系统整体性能。

在本篇博文中,您将学到以下知识

  • 概览
  • 什么是异步序列?
  • 创建自定义异步序列
  • 另一种创建异步序列的方式:AsyncStream
  • 取消异步序列的处理
  • 总结

什么是异步序列?

异步序列(AsyncSequence)严格的说是一个协议,它为遵守者提供异步的、序列的、迭代的序列元素访问。

在这里插入图片描述

表面看起来它是序列,实际上它内部元素是异步产生的,这意味着当子元素暂不可用时使用者将会等待直到它们可用为止:
在这里插入图片描述

诸多系统框架都对异步序列做了相应扩展,比如 Foundation 的 URL、 Combine 的发布器等等:

// Foundation
let url = URL(string: "https://kinds.blog.csdn.net/article/details/132787577")!
Task {
    do {
        for try await line in url.lines {
            print(line)
        }
    }catch{
        print("ERR: \(error.localizedDescription)")
    }
}

// Combine
let p = PassthroughSubject<Int,Never>()
for await val in p.values {
    print(val)
}

如上代码所示,URL#lines 和 Publisher#values 属性都是异步序列。

除了系统已为我们考虑的以外,我们自己同样可以非常方便的创建自定义异步序列。

创建自定义异步序列

一般来说,要创建自定义异步序列我们只需遵守 AsyncSequence 和 AsyncIteratorProtocol 协议即可:

在这里插入图片描述

下面我们就来创建一个“超级英雄们(Super Heros)”的异步序列吧:

struct SuperHeros: AsyncSequence, AsyncIteratorProtocol {
    
    private let heros = ["超人", "钢铁侠", "孙悟空", "元始天尊", "菩提老祖"]
    
    typealias Element = String
    var index = 0
    
    mutating func next() async throws -> Element? {
        defer { index += 1}
        
        try? await Task.sleep(for: .seconds(1.0))
        
        if index >= heros.count {
            return nil
        }else{
            return heros[index]
        }
    }
    
    func makeAsyncIterator() -> SuperHeros {
        self
    }
}

Task {
    let heros = SuperHeros()
    for try await hero in heros {
        print("出场英雄:\(hero)")
    }
}

以上异步序列会每隔 1 秒“产出”一名超级英雄:

在这里插入图片描述

如上代码所示,如果下一个超级英雄还未就绪,系统会在等待同时去执行其它合适的任务,不会有任何资源上的浪费。

另一种创建异步序列的方式:AsyncStream

其实,除了直接遵守 AsyncSequence 协议以外,我们还有另外一种选择:AsyncStream!

不像 AsyncSequence 和 AsyncIteratorProtocol 协议 ,AsyncStream 是彻头彻尾的结构(实体):
在这里插入图片描述

它提供两种构造器,分别供正常和异步序列产出(Spawning)情境使用:

public init(_ elementType: Element.Type = Element.self, bufferingPolicy limit: AsyncStream<Element>.Continuation.BufferingPolicy = .unbounded, _ build: (AsyncStream<Element>.Continuation) -> Void)

    
public init(unfolding produce: @escaping () async -> Element?, onCancel: (@Sendable () -> Void)? = nil)

下面为此举两个  官方提供的代码示例:

let stream_0 = AsyncStream<Int>(Int.self,
                    bufferingPolicy: .bufferingNewest(5)) { continuation in
     Task.detached {
         for _ in 0..<100 {
             await Task.sleep(1 * 1_000_000_000)
             continuation.yield(Int.random(in: 1...10))
         }
         continuation.finish()
    }
}

let stream_1 = AsyncStream<Int> {
    await Task.sleep(1 * 1_000_000_000)
    return Int.random(in: 1...10)
}

更多关于异步序列的知识,请小伙伴们移步如下链接观赏:

  • Swift异步序列构造器AsyncStream内部定时器(Timer)无法被触发的解决
  • Swift async/await 并发中如何将任务组(TaskGroup)转换为异步序列(AsyncSequence)

取消异步序列的处理

我们知道  新的 async/await 并发模型主打一个“结构化”,之所以称为“结构化”一个重要原因就是并发中所有任务都共同组成一个层级继承体系,当父任务出错或被取消时,所有子任务都会收到取消通知,异步序列同样也不例外。

就拿下面倒计时异步序列来说吧,它能感应父任务取消事件的原因是由于其中调用了 Task.sleep() 方法( sleep() 方法内部会对取消做出响应):

let countdown = AsyncStream<String> { continuation in
    Task {
        for i in (0...3).reversed() {
            try await Task.sleep(until: .now + .seconds(1.0), clock: .suspending)
            
            guard i > 0 else {
                continuation.yield(with: .success("🎉 " + "see you!!!"))
                return
            }
            
            continuation.yield("\(i) ...")
        }
    }
}

Task {
    for await count in countdown {
        print("current is \(count)")
    }
}

正常情况下,我们应该在异步序列计算昂贵元素之前显式检查 Cancel 状态:

let stream_1 = AsyncStream<Int> {
    // 假设 spawn() 是一个“昂贵”方法
    func spawn() -> Int {
        Int.random(in: 1...10)
    }
    
    // 或者使用 Task.checkCancellation() 处理异常
    if Task.isCancelled {
        return nil
    }
    
    return spawn()
}

在某些情况下,我们希望用自己的模型(Model)去关联退出状态,这时我们可以利用 withTaskCancellationHandler() 方法为异步序列保驾护航:

public func next() async -> Order? {
    return await withTaskCancellationHandler {
        let result = await kitchen.generateOrder()
        // 使用自定义模型中的状态来判断是否取消
        guard state.isRunning else {
            return nil
        }
        return result
    } onCancel: {
    	// 在父任务取消时设置取消状态!
        state.cancel()
    }
}

注意,当父任务被取消时上面 onCancel() 闭包中的代码会立即执行,很可能和 withTaskCancellationHandler() 方法主体代码同步进行。

现在,在一些 Task 内置取消状态不适合或不及时的场合下,我们可以在异步序列中使用 withTaskCancellationHandler() 的 onCancel() 子句来更有效率的完成退出操作,棒棒哒!💯。

总结

在本篇博文中,我们首先简单介绍了什么是异步序列,接着学习了几种创建自定义异步序列的方法,最后我们讨论了如何优雅的取消异步序列的迭代。

感谢观赏,再会!😎

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

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

相关文章

BGP选路的十一条原则(第八课)

华为官网:有详细的BGP11条选路原则介绍 NetEngine AR 产品文档 (huawei.com) 第一 BGP 属性 第二 BGP 十一条选路原则 实操部分

5个小功能掌握正则表达式的使用

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ 这篇文章通过实现5个小功能&#xff0c;来讲述在Java中如何使用正则。 Java中如何使用正则 Java中正则相关类位于java.util.regex包下&#xff0c;主要使用2个类&#xff0…

【Maven入门篇】(1)详细讲解Maven的安装报错解决

&#x1f38a;专栏【Maven入门篇】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【The truth that you leave】 &#x1f970;欢迎并且感谢大家指出我的问题 文章目录 &#x1f33a;Maven介绍⭐作用⭐官网 &#x1f384;maven安…

mysql 5.7 修改密码

为了提高安全性 mysql5.7中user表的password字段已被取消&#xff0c;取而代之的事 authentication_string 字段&#xff0c;当然我们更改用户密码也不可以用原来的修改user表来实现了。下面简绍几种mysql5.7下修改root密码的方法&#xff08;其他用户也大同小异&#xff09;。…

整洁架构能有多整洁?

前段时间&#xff0c;我将一个中大型后端项目顺利地从Spring Boot 2.5 迁移到了Spring Boot 3&#xff0c;整个过程仅花了一天左右时间&#xff0c;在小酌庆祝之余&#xff0c;我开始思考此次迁移之所以能够顺利进行的原因&#xff0c;此时一个概念立即闪现脑海——整洁架构。 …

ARM Cortex-M内核中系统堆栈

文章目录 有无OS的栈结构区别&#xff1a;裸机的任务栈结构带FreeRTOS操作系统的任务栈 ARM的寄存器有哪些特殊寄存器有哪些 关于FreeRTOS中的SP寄存器栈操作【压栈与弹栈的操作】一般函数嵌套调用时sp指针的变化Cortex-M内核的MSP与PSP作用 有无OS的栈结构区别&#xff1a; 裸…

雷达设备问题(从另一个角度看待区间合并 + 贪心思路 + 未发现关键的错误样例)

雷达设备问题 文章目录 雷达设备问题前言题目描述题目分析代码详解错误案例分析&#xff1a;思路代码&#xff1a; 前言 对于区间合并问题&#xff0c;我们一般会将区间按照左端点或者是右端点进行排序&#xff0c;至于其中的选择要依据题目的分析&#xff0c;这里给大家用贪心…

Nginx 设置禁用 OPTIONS 请求

1、修改 nginx 配置 在 nginx.conf 配置文件中&#xff0c;增加如下内容&#xff1a; if ($request_method ~* OPTIONS) {return 403; }效果如下&#xff1a; 2、重启 nginx 服务 systemctl restart nginx或者 service nginx restart3、功能验证 使用如下命令&#xff…

【Java毕设项目】基于SpringBoot+Vue教务管理系统的开发与实现

博主主页&#xff1a;一季春秋博主简介&#xff1a;专注Java技术领域和毕业设计项目实战、Java、微信小程序、安卓等技术开发&#xff0c;远程调试部署、代码讲解、文档指导、ppt制作等技术指导。主要内容&#xff1a;毕业设计(Java项目、小程序、安卓等)、简历模板、学习资料、…

提升群辉AudioStation音乐体验,实现公网音乐播放

文章目录 本教程解决的问题是&#xff1a;按照本教程方法操作后&#xff0c;达到的效果是本教程使用环境&#xff1a;1 群晖系统安装audiostation套件2 下载移动端app3 内网穿透&#xff0c;映射至公网 很多老铁想在上班路上听点喜欢的歌或者相声解解闷儿&#xff0c;于是打开手…

Unity WebSocket-Server

&#x1f33c;WebSocket-Server &#x1f96a;效果展示&#x1f32d;启动Server&#x1f371;连接Server &#x1f96a;效果展示 在Unity中创建WebSocket服务器&#xff0c;从网页连接到该服务器进行消息通信&#xff0c;在Unity中接收到的消息都在主线程中 &#x1f32d;启…

基于ssm的学生综合测评管理系统047

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

C\C++ 设置Visual Studio编译器使用C++17标准

文章作者&#xff1a;里海 来源网站&#xff1a;王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C-CSDN博客 简介&#xff1a; 使用ISO C17标准可以为开发人员带来许多好处&#xff0c;包括更简洁的代码、更高的运行效率、更好的硬件支持、更好的兼容性和可移植性&am…

基于springboot+vue的重庆旅游网(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

二、逻辑回归算法(LR,Logistic Regression)(有监督学习)

一、算法思路 逻辑回归本质就是基于多元线性回归&#xff0c;多元线性回归就是yw0 w1*x1 w2*x2 ... wn*xn 多元函数的值域是(-∞&#xff0c;∞)&#xff0c;逻辑回归就是将值域映射到(0,1)之间&#xff0c;因为这样就可以变成一个概率值。常用的方法是将多元函数求解得到…

vscode调试webpack项目的方法

vscode调试webpack项目的方法 首先安装vscode插件Javascript Debugger 这个插件的介绍也写清楚了&#xff1a; An extension for debugging Node.js programs and Chrome. 那就是用来调试Node.js和Chrome的vscode扩展插件&#xff0c;包括typescript. 然后按F5启动调试&…

【LeetCode-简单题】589. N 叉树的前序遍历

文章目录 题目方法一&#xff1a;单循环栈做法方法二&#xff1a;递归 题目 方法一&#xff1a;单循环栈做法 关键在于子节点的入栈顺序&#xff0c;决定了子节点的出栈顺序&#xff0c; 因为是前序遍历 所以压栈顺序先让右边的入栈 依次往左 这样左边的节点会在栈顶 这样下次…

Linux CentOS7 vim多文件编辑

使用vim编辑多个文件&#xff0c;十分常用的操作。本文从打开、显示、切换文件到退出&#xff0c;进行简单讨论。 一、打开文件 1.一次打开多个文件 vim还没有启动的时候&#xff0c;在终端里输入vim file1 file2 … filen便可以打开所有想要打开的文件。 执行命令 vim fil…

深入解析哈希表、哈希映射和并发哈希映射的区别,以及死锁的成因和解决方案

目录 死锁死锁产生条件解决方案 HashTableConcurrentHashMapHashMap 死锁 死锁是多线程编程中常见的问题&#xff0c;当两个或多个线程互相等待对方持有的资源而无法继续执行时&#xff0c;就会发生死锁。这种情况下&#xff0c;程序会陷入无法恢复的状态&#xff0c;造成程序…

215. 数组中的第K个最大元素+17.14. 最小K个数(优先级队列)

目录 一、第K个最大元素 二、代码 三、最小K个数 四、代码 一、第K个最大元素 215. 数组中的第K个最大元素 - 力扣&#xff08;LeetCode&#xff09; 二、代码 class Solution { public:int findKthLargest(vector<int>& nums, int k) {priority_queue data(nu…