Swift 异步与并发

当你调用一个同步函数时,你的线程是阻塞的,等待函数完成,在它完成之前,你的线程不能做其他任何事情。调用一个异步操作后,当前线程不会被阻塞。异步不一定是并发。并发是在同一时间段内有多个任务同时推进(通过多线程交替或多核并行实现)。

我们来看异步的例子。

swift 复制代码
class ViewController: UIViewController {
    @IBAction func buttonTapped(_ sender: UIButton) {
        DispatchQueue.main.async {
            print("1")
        }

        print("2")
    }
}

ViewController 是 MainActor 隔离的,buttonTapped(:) 也是 MainActor 隔离的,即运行在主线程。buttonTapped(:) 把闭包放入队列,继续执行 print("2")。闭包执行是异步的,但不是并发的,闭包必须等待主线程上的任务全部执行完毕、RunLoop 进入下一轮循环后,才会被主线程按顺序执行。

swift 复制代码
class ViewController: UIViewController {
    @IBAction func buttonTapped(_ sender: UIButton) {
        foo()
    }

    func foo() {
        Task {
            print("1")
        }

        print("2")
    }
}

由于方法 foo() 是 MainActor 隔离的,Task 默认与 foo() 有相同的 actor 隔离,因此 Task 的闭包也是 MainActor 隔离的。建立 Task 后,不阻塞主线程,继续执行 print("2")。Task 的闭包与 foo() 方法异步执行,都在主线程,但不是并发。

如果方法 foo() 是非 actor 隔离的,则 Task 也是非 actor 隔离的。

swift 复制代码
class ViewController: UIViewController {
    @IBAction func buttonTapped(_ sender: UIButton) {
        foo()
    }
    
    nonisolated func foo() {
        Task {
            print("1")
        }

        print("2")
    }
}

foo() 方法在主线程被调用,运行在主线程,Task 的闭包运行在非主线程,异步,并且是并发。

即使 foo() 是 MainActor 隔离的,如果调用 Task.detached,Task 的闭包没有任何 actor 隔离,运行在非主线程,异步,并且是并发。

swift 复制代码
class ViewController: UIViewController {
    @IBAction func buttonTapped(_ sender: UIButton) {
        foo()
    }
    
    func foo() {
        Task.detached {
            print("1")
        }

        print("2")
    }
}

再看看异步函数,我们把 foo() 改为异步,buttonTapped(_:) 是同步方法,需要一种方式由同步进入异步。接决方案是前面的 Task。

swift 复制代码
class ViewController: UIViewController {
    @IBAction func buttonTapped(_ sender: UIButton) {
        Task {
            print("1")
            await foo()
            print("2")
        }
    }
    
    func foo() async {
        print("3")
    }
}

在 Task 的闭包中调用 foo(),buttonTapped(_:) 是 MainActor 隔离的,Task 也是MainActor 隔离的,Task 闭包运行在主线程。print("1") 运行在主线程,await foo() 使闭包挂起,系统会使用主线程来做其他工作,foo() 也是 MainActor 隔离的,在主线程执行 foo(),foo() 返回,接着在主线程恢复运行 print("2")。foo() 和 Task 闭包是异步的,但不并发。

如果使 foo() 非 actor 隔离,

swift 复制代码
class ViewController: UIViewController {
    @IBAction func buttonTapped(_ sender: UIButton) {
        Task {
            print("1")
            await foo()
            print("2")
        }
    }
    
    nonisolated func foo() async {
        print("3")
    }
}

与上面的唯一的区别是 foo() 运行在别的线程。foo() 和 Task 闭包是异步的,并且是并发。 如果 Task 闭包非 actor 隔离,

swift 复制代码
class ViewController: UIViewController {
    @IBAction func buttonTapped(_ sender: UIButton) {
        Task.detached {
            print("1")
            await self.foo()
            print("2")
        }
    }
    
    nonisolated func foo() async {
        print("3")
    }
}

Task 闭包中 print("1") 在非主线程执行,await self.foo() 使闭包挂起,系统会使用当前线程来做其他工作,foo() 运行在别的线程,返回,闭包恢复执行 print("2"),但线程可能与 print("1") 线程不同。foo() 和 Task 闭包是异步的,并且是并发。

如果异步方法 foo() 内部调用另一个异步函数,则分析过程与 Task 闭包调用 foo() 类似。

异步函数与前面的异步不同,调用异步函数会使当前线程挂起,系统会使用当前线程来做其他工作。相同点是都不会阻塞当前线程,而同步会阻塞当前线程。

以上代码在Xcode26.6 (Swift version 6.3.3) 验证。

参考资料

​认识 Swift 中的 async/await - WWDC21 - 视频 - Apple Developer

相关推荐
传奇开心果编程13 小时前
【SwiftUI入门练中学】第7课 动画与手势:让界面更灵动
学习·macos·ui·ios·swiftui·swift
传奇开心果编程1 天前
【SwiftUI娓娓道来】第3课:让界面活起来——交互与动画指南
学习·macos·ui·ios·swiftui·swift
用户38034165882972 天前
控制中心一键后台录音:我被自己的 Spike 骗了两次
ios·swift
东坡肘子2 天前
iPhone Duo 留给开发者的一个月 -- 肘子的 Swift 周报 #155
人工智能·swiftui·swift
茶底世界之下3 天前
导出进度不是一个百分比:AVFoundation 收尾阶段的状态建模
ios·swift
茶底世界之下3 天前
为什么媒体源不能在回调仍执行时提前报告 finish?
swift
茶底世界之下3 天前
为什么视频处理器必须提前声明“它能处理什么帧”
swift
茶底世界之下3 天前
录制结束,不代表视频文件已经交付:AVFoundation 录制完成状态的工程化判断
swift
茶底世界之下9 天前
为什么预览、录制、离线导出不能共享同一个背压策略?
ios·swift
2501_915909069 天前
移动端开发工具链怎么组合比较好,iOS、Android、跨端三套配置方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程