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

相关推荐
东坡肘子2 天前
参与学生移动应用大赛有感 -- 肘子的 Swift 周报 #161
人工智能·swiftui·swift
songgeb6 天前
YYImage Bug:一行之差,正常 JPEG 如何在 iOS 被读成条纹
ios·swift
fthux6 天前
Tick Rest,一个休息提醒工具的自我修养
macos·开源·swift
Fatbobman(东坡肘子)7 天前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
开发语言·人工智能·redis·ios·swiftui·iphone·swift
chaoahyl7 天前
WaterDiary 喝水记录 App 实战(2)· 实现喝水首页与本地保存
swift
茶底世界之下9 天前
iPhone Duo 适配实战,Awaking 踩过的 8 个坑
ios·swiftui·swift
xbzb9 天前
Swift 对象存储建环实验 + Ceilometer 计量:两个收尾服务
swift·ring·实验·ceilometer·象存储·建环
2501_916008899 天前
iOS App 图标、启动屏和多语言搭配 方法,上架前使用KXBuild一次做好 App 图标
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_915918419 天前
iOS 推送通知怎么实现,从 APNs p8 密钥到 iOS 真机收到第一条推送
vscode·ios·objective-c·个人开发·swift·敏捷流程
东坡肘子9 天前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
人工智能·swiftui·swift