如何在 Swift 命令行程序执行异步代码
最近在学习多线程底层源码, 为了方便验证各种多线程案例, 我是直接在一个 main.swift
文件中编写异步代码, 然后终端中使用 swift main.swift
命令来执行看输出的. 然后就遇到了一个很基础的问题: 命令行程序不像 iOS 或 macOS 应用, 默认是没有 runloop 的, 这就代表提交到主线程的同步代码执行完成后整个程序就退出了, 我们添加到其他线程的或者在主线程异步执行的代码就没有机会被执行了.
这个问题解决不了的话就无法使用终端程序验证多线程代码了, 经过一番学习, 发现是有多种方式能使终端程序不退出执行的, 这篇文章将我谈索道的这些方式做一下总结
开启 runloop
我们命令行程序运行完即结束是因为没有 runloop 运行, 那我们手动调用一下 runloop 运行就可以了
import Foundation
setbuf(stdout, nil)
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
while true {
print(Date().timeIntervalSince1970)
sleep(1)
}
}
RunLoop.main.run(mode: .default, before: .distantFuture)
// CFRunLoopRun() // 与上面功能相同
// CFRunLoopStop(CFRunLoopGetMain())
dispatchMain
dispatchMain
是系统提供的 API, 其文档描述是这样的:
This function “parks” the main thread and waits for blocks to be submitted to the main queue.
简言之, 这个方法会停在主线程上, 然后等待执行后续提交到主线程上的任何闭包
import Foundation
setbuf(stdout, nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
while true {
print(Date().timeIntervalSince1970)
sleep(1)
}
}
dispatchMain()
之后在需要退出的地方调用 exit()
即可退出程序
await
import Foundation
setbuf(stdout, nil)
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
while true {
print(Date().timeIntervalSince1970)
sleep(1)
}
}
try await Task.sleep(for: .seconds(100_000_000))
信号量
还有一种信号量的方式, 我们都知道使用 signal()
使信号量加一, wait()
使信号量减一, 同时如果减一后的信号量为负则会一直等待直到信号量大于等于 0
import Foundation
let semaphore = DispatchSemaphore(value: 0)
// 这里不能使用主队列, 否则不执行
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
while true {
print(Date().timeIntervalSince1970)
sleep(1)
semaphore.signal()
}
}
semaphore.wait()
不过这种方式有一个缺点, 就是 wait()
方法是通过使当前所在线程阻塞的方式实现等待, 所以如果我们在主线程上调用了 wait()
, 那我们就必须在其他线程执行 signal()
才能解除主线程的等待状态了.
Ref
本博客文章采用 CC 4.0 协议,转载需注明出处和作者。