Swift并发学习笔记

  • 如何使用
    • async await

      • async 用于标记方法为异步方法
      • 执行异步方法时需要加上await
      • 异步方法执行环境是在一个Task中
      • 将一个基于回调(callback)的异步 API 转换为 Swift Concurrency 的 async/await 风格
        • withCheckedThrowingContinuation
        • withUnsafeContinuation
          func loadImage(from url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
             DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
                 if let image = UIImage(systemName: "bolt") {
                     completion(.success(image))
                 } else {
                     completion(.failure(NSError(domain: "ImageError", code: 1)))
                 }
             }
          }
          
          func loadImageAsync(from url: URL) async throws -> UIImage {
             try await withCheckedThrowingContinuation { continuation in
                 loadImage(from: url) { result in
                     switch result {
                     case .success(let image):
                         continuation.resume(returning: image)
                     case .failure(let error):
                         continuation.resume(throwing: error)
                     }
                 }
             }
          }
          
    • Task

      • 最基本的 Task 创建方式

        func fetchSomething() async -> String {
            return "Fetched data"
        }
        
        // 启动一个任务
        Task {
            let result = await fetchSomething()
            print("Result: \(result)")
        }
        
        
      • Task 的几种变体

        • 使用 Task 返回值 & 捕获错误

              let task = Task {
                  try await fetchData()
              }
              
              do {
                  let data = try await task.value
              } catch {
                  print("Error: \(error)")
              }
          
        • 使用 Task.detachedTask.detached 会脱离当前上下文,比如不继承 @MainActor, 适合执行不依赖当前 actor 的后台任务

               Task.detached {
               // 独立于当前上下文(如 actor 或 MainActor)
               let value = await computeInBackground()
               print(value)
           }
        
      • 使用任务优先级

            Task(priority: .userInitiated) {
                await loadData()
            }
                    ```
        
        
    • Task group

      • 调用group中的cancelAll方法时,系统会向所有的子任务发送取消信号,通过Task.isCanceled 为 true 可以判断
      • 子任务需要正常处理这个取消信号,才会终止任务的执行。try Task.checkCancellation()
          func runAllTasks() async throws {
             started = Date()
             try await withThrowingTaskGroup(of: Result<String, Error>.self) { [unowned self] group in
                 let batchSize = 1
                 for index in 0..<batchSize {
                     group.addTask {
                         await self.worker(number: index)
                     }
                 }
                 // 1
                 var index = batchSize
                 // 2
                 for try await result in group {
                     switch result {
                     case .success(let result):
                         print("Completed:\(result))")
                     case .failure(let error):
                         print("Failed: \(error.localizedDescription)")
                     }
                     // 3
                     if index < total {
                         group.addTask { [index] in
                             await self.worker(number: index)
                         }
                         index += 1
                     }
                 }
                 await MainActor.run {
                     completed = 0
                     countPerSecond = 0
                     scheduled = 0
                 }
             }
         }
      
         func worker(number: Int) async -> Result<String, Error> {
             await onScheduled()
             let task = ScanTask(input: number)
             let result: Result<String, Error>
             do {
                 result = try .success(await task.run())
             } catch {
                 result = .failure(error)
             }
             await onTaskCompleted()
             return result
         }
       ```
      
    • AsyncSequence

      protocol AsyncSequence {
          associatedtype Element
          func makeAsyncIterator() -> AsyncIterator
      }
      
      • 类似 Sequence(同步迭代器)但用于异步迭代
      • 使用 for await 来异步消费数据流
      • Swift 提供了很多 conform 的类型,比如:
        • URLSession.AsyncBytes
        • AsyncThrowingStream
        • AsyncStream
        • AsyncLazySequence
    • AsyncStream

      • 是AsyncSequence的一种实现

      • 核心用途: 把一个基于回调或事件推送的数据源,转换成用for await消费的异步序列

        func makeTimerStream(interval: TimeInterval) -> AsyncStream<Date> {
           AsyncStream { continuation in
                   let timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in
                       continuation.yield(Date())
                   }
           
                   continuation.onTermination = { @Sendable _ in
                       timer.invalidate()
                   }
               }
        }
        
           Task {
               for await time in makeTimerStream(interval: 1) {
                   print("Current time: \(time)")
           }
             
           extension NotificationCenter {
                 func notifications(for name: Notification.Name) -> AsyncStream<Notification> {
                   AsyncStream<Notification>.init { continuation in
                     NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil) { notification in
                       continuation.yield(notification)
                     }
                   }
                 }
               }
        } 
        
  • Swift并发模型
    • Swift 并发模型(Swift Concurrency Model)是 Apple 为 Swift 提供的一套现代并发编程机制,它从语言层面解决了多线程开发中常见的数据竞争、线程爆炸、回调地狱、不易维护等问题
    • 一句话理解:Swift 并发模型是一套基于 async/await、actor、任务调度、隔离机制的编程模型,旨在让并发代码像同步代码一样安全、可读、可维护。
    • Swift 并发模型包含哪些核心组成:
      • async/await 简洁地编写异步代码 let result = try await fetchData()
      • Task 在并发环境中启动新任务 Task { await doSomething() }
      • TaskGroup ThrowingTaskGroup 并发执行多个任务,并合并结果|并发请求多个接口
      • actor 对象级别的线程隔离,保护状态不被数据竞争actor Counter { var value = 0 }
      • @MainActor @globalActor 明确指定执行上下文(主线程或其他共享线程)@MainActor class ViewModel {}
      • Sendable 编译期检查跨线程传递的数据是否安全|struct User: Sendable
      • @TaskLocal 提供任务内的线程安全局部变量 @TaskLocal static var currentUser
  • actor模型
    • actor 是线程隔离的并发单元,它的代码默认不在主线程,但所有对其内部状态的访问都是自动串行调度、线程安全的。
    • actor 内部状态只能被一个任务访问,避免数据竞争(Data Race)
    • actor 并不是“多线程运行”,而是“串行调度在某个线程上(可能是主线程,也可能是后台线程)”
    • 作用范围:每个实例隔离
    • nonisolated 关键字
      • actor中的方法是默认隔离的
      • 当方法不需要访问actor中内部状态时,可以加上 nonisolated 不隔离
      • nonisolated修饰的方法,要确保线程是安全的。
      • nonisolated修饰的方法,节约性能,不需要actor之间跳转
      • 隔离的目的是为了确保方法在执行时,访问安全
    • MainActor
      • @MainActor 标记的所有的代码都是在主线程中执行,并且线程安全
      • Swift 编译器和运行时会保证这些代码在主线程执行
      • 所以只要正确使用 @MainActor,就能避免 UI 线程访问错误
      • 无需手动跳线程(不需要 DispatchQueue.main.async)
    • globalActor
      • global actor 是全局作用域的 actor,允许你将某类代码指定为“总是由某个共享 actor 串行执行”,以实现线程隔离和一致性调度。所有标记代码共享同一实例, 比如:全局日志、UI、数据库访问等统一上下文。
      • 最常见的 global actor 就是:@MainActor
      • 为什么需要 global actor
        • 普通 actor实例级别的线程隔离,每个 actor 实例拥有自己的队列。
        • global actor全局级别的线程隔离,多个实例共享一个执行上下文(例如主线程)。
          @globalActor
          actor Database {
              static let shared = Database()
              private var manager: DatabaseManager!
              
              func setUp() async {
                  manager = await DatabaseManager()
              }
          }
          
          @Database
          class DatabaseManager {
              init() {}
              
              func saveData(_ data: String) async { }
          }
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容