使用 Swift 中的新并发将同步函数转换为异步函数

2024-03-18

我想将同步函数转换为异步函数,但我不知道正确的方法是什么。

假设我有一个需要很长时间才能获取数据的同步函数:

func syncLongTimeFunction() throws -> Data { Data() }

然后我在下面的函数中调用它,它仍然是一个同步函数。

func syncGetData() throws -> Data {
    return try syncLongTimeFunction()
}

但现在我想将其转换为异步函数。下面正确的做法是什么:

  • 第一种方式:

    func asyncGetData() async throws -> Data {
        return try syncLongTimeFunction()
    }
    
  • 第二种方式:

    func asyncGetData2() async throws -> Data {
        return try await withCheckedThrowingContinuation { continuation in
            DispatchQueue.global().async {
                do {
                    let data = try self.syncLongTimeFunction()
                    continuation.resume(returning: data)
                } catch {
                    continuation.resume(throwing: error)
                }
            }
        }
    }
    

我认为第一种方法就足够了,就像罗布在下面的回答一样。但是当我在主线程上调用如下所示的异步函数时,syncLongTimeFunction()也在主线程上处理。所以它会阻塞主线程。

 async {       
     let data = try? await asyncGetData()
 }

简而言之,这取决于慢速/同步函数的性质:

  1. 它是否是一些相对短暂的同步任务,而您正试图避免 UI 中出现短暂的故障?

    在这种情况下,常见的建议通常是“独立”任务:

    func asyncGetData() async throws -> Data {
        try await Task.detached {
            try self.someSyncFunction()
        }.value
    }
    

    但这是非结构化并发 https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency#Unstructured-Concurrency,因此您自己承担支持任务取消的负担。所以像下面这样的东西可能更合适:

    func asyncGetData() async throws -> Data {
        let task = Task.detached {
            try self.longTimeFunction()
        }
        return try await withTaskCancellationHandler {
            try await task.value
        } onCancel: {
            task.cancel()
        }
    }
    

    (不用说,这是假设longTimeFunction甚至支持取消。下面详细介绍一下。它还假设longTimeFunction与相关参与者并不孤立;您可能想确保它是非隔离的。)

    或者,从 Swift 5.7 开始(感谢SE-0338 https://github.com/apple/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md),您可以享受结构化并发,并通过非隔离的方式将其从当前参与者中分离出来async功能:

    nonisolated func asyncGetData() async throws -> Data {
        try self.longTimeFunction()
    }
    

    因为这是结构化并发,所以我们免费获得取消传播。而且因为它是非隔离的async函数,它从当前的 actor 中获取它。

    为了完整起见,还有其他选择。但这些是从当前演员手中夺取任务的一些常见方法。

  2. 这个缓慢的函数是否执行一些计算密集型计算?

    为了确保演员能够始终取得“进步”,您需要定期Task.yield。在这种情况下,您还需要确保它定期检查取消情况,并使用checkCancellation https://developer.apple.com/documentation/swift/task/checkcancellation() or isCancelled https://developer.apple.com/documentation/swift/task/iscancelled-swift.type.property, too.

    例如:

    func longTimeFunction() async throws -> Data {
        …
    
        for i in … {
            try Task.checkCancellation()
            await Task.yield()
            …
        }
    
        return …
    }
    

    现在,您检查取消和收益的频率由您决定。我通常会在计算速度和我想要检查的频率之间取得一些平衡(例如,if i.isMultiple(of: 100) {…}),但是,希望您能明白这一点。

  3. 或者它确实是一个缓慢的同步函数,您无法轻松重构以支持 Swift 并发?

    然后,我们必须意识到,Swift 并发是基于契约的,以确保任务始终能够取得进展。

    具体来说,SE-0296 - 异步/等待 https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md警告我们要么必须提供某种方式在 Swift 并发中交错(例如,使用Task.yield如前一点所述)或者我们“通常在单独的上下文中运行它”:

    由于潜在的挂起点只能出现在异步函数中显式标记的点处,因此长计算仍然会阻塞线程。当调用只执行大量工作的同步函数时,或者遇到直接在异步函数中编写的特别密集的计算循环时,可能会发生这种情况。在任何一种情况下,线程都无法在这些计算运行时交错代码,这通常是正确性的正确选择,但也可能成为可扩展性问题。需要进行密集计算的异步程序通常应该在单独的上下文中运行。

    Swift 演进提案没有明确定义“在单独的上下文中运行”。但在 WWDC 2022 视频中可视化和优化 Swift 并发 https://developer.apple.com/videos/play/wwdc2022/110350/?time=1186,他们建议 GCD:

    如果您有需要执行这些操作的代码,请将该代码移出并发线程池(例如,通过在调度队列上运行它),并使用延续将其桥接到并发世界。只要有可能,请使用异步 API 进行阻塞操作,以保持系统平稳运行。

    简而言之,在这种情况下,您将采用选项 2,将 GCD 代码封装在withCheckedThrowingContinuation。显然,我们必须担心 GCD 的所有传统问题(例如,减轻线程爆炸、手动确保线程安全、避免死锁等)仍然适用。请注意,当您开始使用 GCD API 占用 CPU 核心时,这超出了 Swift 并发协作线程池的范围,因此您仍然可能会遇到 CPU 过度使用,而该池旨在缓解这一问题。

因此,底线完全取决于同步和/或长时间运行例程的性质。不幸的是,如果没有更多信息,就不可能更具体syncLongTimeFunction.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Swift 中的新并发将同步函数转换为异步函数 的相关文章

  • 我的所有 UIAlertController 消息都变成了单行

    我不确定这是否是罪魁祸首 但我对这个项目所做的最大改变是几天前升级到 Swift 4 我知道我的 UIAlertController 消息在需要时显示多行 但今天我偶然意识到它们全部变成了单行并且末尾有省略号 由于我从 API 显示这些消息
  • 如何在 Option::and_then 或 Option::map 闭包中使用 async/await 而不使用 OptionFuture?

    我想运行类似以下代码的代码 async fn get user s str gt Option
  • 读取/写入本地 json 文件 swift 4

    请帮我 我在项目中添加了一个json文件 我的 json 文件 person title image Vitamin1 favorite false title B6 image Vitamin2 favorite false 我可以读取文
  • 如何将 UTF16 字符串解码为 Unicode 字符

    设备将字符串 编码为 uD83E uDD1B uD83C uDFFD 该字符串中表示的十六进制数字来自字符的 UTF 16 十六进制编码 Unicode 代码点U 1F91B U 1F3FD从 UTF 32 十六进制编码获取其数字 就拿后面
  • xCode Cocoapods 构建失败“架构 x86_64 的未定义符号”

    我想为模拟器和真实设备构建我的 Xcode 项目 本地反应和快速 模拟器效果很好 今天我尝试为我的设备构建它 我在 Xcode 栏中选择了我的设备并添加了要发布的方案 我必须这样做 因为我使用的是 React Native 否则捆绑包未打包
  • Xcode 不再识别测试

    我已经解决这个问题几天了 但我没有任何想法 我在 Xcode 中使用单元测试 效果很好 突然 Xcode 不再识别我的测试 如果我进入测试面板 它会显示我的测试为零 我其实有13个 运行单独测试或某些课程的能力现已消失 它不会在编辑器区域的
  • 尝试解码 JSON 日期时显示“JSON 写入中的类型无效 (__NSTaggedDate)”

    当我尝试从具有日期变量的数据库中解码 JSON 对象时 出现错误 由于未捕获的异常 NSInvalidArgumentException 而终止应用程序 原因 JSON 写入中的无效类型 NSTaggedDate 错误发生在以下代码行 le
  • 前置条件失败:从 iOS 13.4 开始,在 SwiftUI 中使用 GeometryReader 时输入索引无效

    昨天 我将 XCode 和我的项目升级到 iOS 13 4 我开始在使用 GeometryReaders 的视图周围看到很多失败 除了 前提条件失败 输入索引无效 之外 该错误没有显示任何内容 升级到 13 4 1 后我遇到了完全相同的问题
  • 如何在 TextField (SwiftUI) 上添加底线

    I use Rectangle 在 TextField SwiftUI 上添加底部边框 但我想用protocol TextFieldStyle对于 TextField 样式的底线 如 RoundedBorderTextFieldStyle
  • UIScrollView 滚动时捕捉到位置

    我正在尝试实现一个捕捉到点的滚动视图滚动时 我在这里看到的所有帖子都是关于在用户结束拖动滚动条 之后 捕捉到某个点的 我想让它在拖动过程中折断 到目前为止 我已经用它来停止拖动后的惯性 并且效果很好 func scrollViewWillE
  • 使用 PushStreamContent 从 HTTPClient 上传

    我想将大量数据上传到网络服务器from客户端机器 我直接跳到 PushStreamContent 这样我就可以直接写入流 因为结果的大小各不相同 并且可能相当大 流程如下 User runs query gt Reader Ready Ev
  • Swift - 如何从 OneSignal SDK 向特定用户名标签发送发布通知?

    如何从 Swift iOS Native SDK 向特定标签 例如 用户名 john 发送发布通知 我之前有过sendTag OneSignal 我正在向下面发送一个带有playerID 的用户 但这还不够 某人可以使用不同的帐户登录 所以
  • 故事板入口点缺失

    在 xcode 7 2 中 对象列表中没有 Storyboard Entry Point 项 我需要使用 Storyboard Entry Point 我通过谷歌搜索找不到任何类似的问题 所以任何人都可以在这里帮助我 单击要作为情节提要入口
  • 是否可以在 RealmSwift 中使用枚举?

    我想做这样的事情 enum WeekDay case Monday Tuesday Wednesday Thursday Friday Saturday Sunday class Person Object dynamic var birt
  • Swit 中的函数式编程将数组元素分配到正确的“桶”

    我是函数式编程的新手 我的问题是我有一个主数组和固定数量的 目标 数组 我想根据每个元素的特定值将主数组中的元素分配到正确的结果数组中 我猜测一种方法是使用一个映射函数来遍历主数组元素 确定正确的 目标数组 值 基于某种逻辑 然后将元素添加
  • 如何判断变量是否是数组

    我有一个接受 Any 的 Swift 函数 我希望它能够接受字符串数组 整数数组 混合数组或数组数组等 它也可以只接受字符串或整数 等等 不在数组中 所以我有这个 private func parse parameter Any if pa
  • IssuerSigningKeyResolver 调用异步方法

    我们使用 IssuerSigningKeyResolver 它是 Microsoft IdentityModel Tokens 的一部分 用于令牌验证并接受非异步委托 我们调用一个异步方法 这将导致阻塞调用 因此想知道使用它的正确方法是什么
  • 为什么 'self.self' 在 swift 中编译并运行?

    昨天我回顾了 Swift 中的一段代码 其中包括这一行 self self someProperty 这让我很惊讶 因为这个词self被保留并用作对当前实例的引用 起初我用其他语言检查了这种现象 但都给出了错误 这并不奇怪 但是 为什么它能
  • Swift 从照片库中获取视频的 NSData

    我使用 UIImagePickerController 从我的库中选择视频 我需要提取视频文件的 NSData 我使用以下操作从我的库中选择视频 但我的数据似乎为零 但是我的 AVPlayer 播放生成的 NSURL 中的视频 所以我知道问
  • FindAsync 很慢,但是延迟加载很快

    在我的代码中 我曾经使用加载相关实体await FindAsync 希望我能更好地遵守 C 异步指南 var activeTemplate await exec DbContext FormTemplates FindAsync exec

随机推荐

  • Nuxt - 将脚本添加到头部和主体

    我正在尝试在我的 Nuxt 应用程序中使用此脚本 但不知道如何操作 在基本的 HTML 文件中 它工作得很好 这是代码
  • Hibernate Envers 修订信息(更改列表)

    我想在我的项目中添加修订更改列表 单击信息图标 例如 Revision X added fieldA entry modified fieladB from B to BB removed fieldC entry 哪个是最好的方法 ps
  • Xcode 连接到 MS SQL 数据库

    我有一个现有数据库已在远程启动并运行MS SQL server 并且我希望能够与该数据库进行通信和交互Xcode 我正在写一份申请OS X in Swift以及应用程序应使用的数据存储在该远程数据库中 问题是我好像找不到Swift可以连接到
  • 如何传递 bquote 的符号字符串以在 ggplot 中求值?

    我在函数中创建的 ggplot 的轴标签有所不同 有些标签有上标 下标 而另一些则没有 例子 m data lt data frame x runif 10 y runif 10 x labs lt c rain mm light W m
  • array_walk 匿名函数

    有没有办法让我用匿名函数来获取这个数组来设置值 url array dog cat fish array walk url function value key url key str replace dog value echo pre
  • Azure CLI 运行命令使用参数调用 RunPowerShellScript

    我一直在尝试在 Azure VM 上运行一个脚本 该脚本需要像这样传递参数 az vm run command invoke g
  • scikit learn:与 GridSearchCV 兼容的自定义分类器

    我已经实现了自己的分类器 现在我想对其运行网格搜索 但出现以下错误 estimator fit X train y train fit params TypeError fit takes 2 positional arguments bu
  • ASP>net MVC 可重用部分

    在 winforms 和 ASP net 中使用 net 几年后 我现在开始进入 MVC 我知道有点晚了 对我来说 一个主要的困惑是可重用 组件 的概念 类似于网络表单中用户控件的概念 例如 我希望在我的网站的会员区域内有许多 小部件 其中
  • OpenCL 内置函数“选择”

    我不清楚内置 OpenCL 函数的目的是什么select 有人可以澄清一下吗 来自 OpenCL 规范 功能选择 基因型a 基因型b 基因型c 返回 对于向量类型的每个分量 结果 i 如果设置了 c i 的 MSB b i a i 在这种情
  • 在 asp.net C# 中使用客户端 ID 和客户端密钥访问 Sharepoint 列表

    目前 我可以使用用户 ID 和密码访问共享点列表 如下所示 但想了解如何使用客户端 ID 和客户端密码访问列表 string siteUrl https xyz sharepoint com sites MyList ClientConte
  • VBA 运行时错误 3134

    以下代码创建一个 SQL 字符串 该字符串在 MS Access 中产生语法错误 3134 sql INSERT INTO tblItems desc descExtended itemNumber currentPrice VALUES
  • Spring Security 加密 MD5

    我有一个使用 spring 框架和 spring security 进行登录的 java web 应用程序 在我的数据库中 我的密码在保存之前已加密为 MD5 我在 application config xml 中添加了这段代码
  • jQuery DataTable - 搜索一列下拉列表

    我有一个简单的 jQuery 数据表 其中包含 4 列 其中一列是下拉列表 table tfoot tr th class searchBox Vendor Location th th class searchBox Currency t
  • RestKit valueTransformer 没有被调用

    我正在使用 RestKit 与我的 JSON 端点对话 端点返回一个以 毫秒数 为单位的 UNIX 时间戳 但是 RestKit 的默认转换器假定它是 秒数 并且我在 NSDate 中得到了错误的值 所以我环顾四周 发现我需要使用自定义 v
  • 在同一请求中创建和更新结构时可能出现的竞争条件 - Coldfusion

    大约一年前 我问了一个关于我在应用程序中遇到的错误的问题 该错误表明可能存在竞争条件 在 ColdFusion 中创建结构体时可能存在竞争条件 https stackoverflow com questions 19859690 possi
  • 使用 clang 在命令行上编译多个 Objective-C 文件

    希望是简单的问题 我正在尝试使用 clang 从命令行学习基本的 Objective C 编译 我知道 Xcode 对于复杂的项目来说是一个更好的解决方案 我计划很快转向它 但我个人觉得如果我可以在终端中手动编译一门语言 我会更好地理解它
  • Ember 过渡和渲染完成事件

    是否触发了任何事件 表明转换 渲染已完成 并且 dom 可见 准备就绪 setupcontroller activate 在 dom 构建 渲染之前 仅当我已经插入一个元素并且我只是将其下面的模型切换出来时 didInsertElement
  • 通过 PHP 执行 .sh 脚本

    我有一些游戏服务器 我需要运行 shell 脚本来提高质量 我试图弄清楚如何通过同一服务器上的网页运行这些脚本 这是一个 Ubuntu 专用服务器 网站文件位于 var www 我需要手动运行的 sh 文件位于 home amservers
  • 在 Lambda 中获取用户的 IP 地址(使用 API 网关和 Python)

    我正在使用这种技术 如何使用 Python 检索 AWS Lambda 公共 IP 地址 https stackoverflow com questions 48619163 how could i retrieve aws lambda
  • 使用 Swift 中的新并发将同步函数转换为异步函数

    我想将同步函数转换为异步函数 但我不知道正确的方法是什么 假设我有一个需要很长时间才能获取数据的同步函数 func syncLongTimeFunction throws gt Data Data 然后我在下面的函数中调用它 它仍然是一个同