1

[随笔] Swift 异步:Task vs DispatchQueue

 11 months ago
source link: https://blog.singee.me/2023/09/21/memos_204/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文同步自「我的 Memos - 随笔」,你也可访问 x.singee.me 查看我的全部碎碎念

在 Swift 上,执行一个异步的函数大体上有两种办法

  • DispatchQueue

背景知识:线程和队列

Swift 同时支持多线程和异步,因此

  • 存在主线程和多个后台线程
  • 每个线程存在若干队列(若干个全局 global 队列(每个优先级一个)、自定义队列(人为创建))

背景知识:队列优先级

在队列层面,存在优先级的概念(在 Task 中叫 priority,在 DispatchQueue 中叫 qos)

  • userInteractive:最高优先级,适用于 UI 操作(例如动画等)(在 Task 中被弃用)
  • userInitiated:较高优先级,适用于用户触发的操作
  • default:中优先级,默认(在 Task 中被弃用)
  • high, medium, low:高中低优先级(仅在 Task 中存在)
  • utility:较低优先级,适用于耗时的后台任务
  • background:最低优先级,适用于耗时的后台任务
  • unspecified:继承于 Thread.current.qualityOfService(在 Task 中被弃用)

背景知识:main

main 同时隐含着两个概念:main thread 和 main queue,事实上无需特意区分(可以认为只有 main thread 才有 main queue,而 main thread 的不同 queue 之间并无特殊区别)

main 的主要用处在于其是直接和用户交互的,只有在 main 才能修改 UI、如果 main 繁忙用户会感觉到 UI 刷新卡顿

  • 对于 Task 而言,在 main 执行使用 Task { @MainActor
  • 对于 DispatchQueue 而言,在 main 执行使用 DispatchQueue.main

注意,Task 的 @MainActor 实际上并不是让闭包代码在 main 执行,而是让其他执行它的 concurrency-aware 代码逻辑在 main 上执行(但是目前存在非 concurrency-aware 的代码,因此可能标记了 @MainActor 实际上仍然是在非 main 执行的,这种时候需要手动切换到 main;不过这种情况 XCode 会有警告,所以不必过于担心)

DispatchQueue 相关

  • 依赖的是 Grand Central Dispatch(GCD) libdispatch
  • Dispatch.main 是在主线程执行的,其他均不保证实际执行线程
  • 执行顺序先进先出
  • 队列类型分为 serial 和 concurrent 两种,serial 在执行完一个任务后才会开始执行另一个、concurrent 会同时执行多个任务(故不保证任务的结束顺序);main 是 serial 类型的、默认也是 serial 类型的
  • 执行时存在 sync 和 async 两种,sync 会等待这个任务完成后返回,async 会直接返回
  • 因上述特性,在 main 线程执行 DispatchQueue.main.sync 会导致死锁
  • 无法直接获得执行结果

Task 相关

  • Task 有 child 和 detached 两种类型,区别在于后者无法访问到调用方可见的变量
  • 来自于 main 创建的 child Task 会始终在 main 执行,否则(除非标记 @MainActor)不保证执行的线程/队列
  • 已提交的任务可取消、可获取(等待)结果(获取结果需要 await)
  • 如果和 DispatchQueue 类比,可以认为 Task 与 DispatchQueue.async 执行上的行为一致

额外:RunLoop

  • RunLoop.current 返回当前的线程循环、.main 返回主线程循环
  • 通常情况下,无需特别注意 RunLoop.main 和 DispatchQueue.main 的区别,二者都是在 main 上执行逻辑
  • RunLoop.main 中执行的逻辑可以被外部用户操作暂停,而 DispatchQueue.main 不会,因此在处理滚动时可能更希望使用 RunLoop.main,而其他通常场景则一般使用 DispatchQueue.main

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK