Swift Concurrency疑问:Task为何未在其他后台线程执行
你遇到的这两个问题其实都是Swift并发模型里的基础规则,咱们一步步拆解:
1. 为什么Task里的代码一开始跑在主线程?
Swift的普通Task {}(非detached的那种)有个关键特性:它会继承你创建它时所在的Actor上下文。在viewDidLoad里,你正处于UIKit的MainActor(主线程对应的Actor)上下文中,所以这个Task会直接绑定到MainActor的执行队列上。
在Task内部,只有当代码执行到await关键字(也就是挂起点)时,才会触发线程切换的可能。你代码里的前两行print都是同步代码,还没到await URLSession.data(from:)这个挂起点,所以自然会在主线程上执行。
如果想让Task从一开始就跑在后台线程,你可以改用Task.detached {},它不会继承当前的Actor上下文,会直接在全局并发池里调度:
Task.detached { do { let data = try await asychronousApiCall() print(data) } catch { print("Request failed with error: \(error)") } }
2. 为什么"starting with asychronousApiCall"在for循环之后才打印?
这涉及到异步任务的调度时机:当你创建Task {}时,它并不会立刻抢占当前线程执行,而是把任务加入到对应Actor的任务队列里等待调度。
你viewDidLoad里的for i in 1...30000是同步执行的代码,会一直占用主线程直到循环完全结束。只有当主线程处理完当前的同步任务栈(也就是viewDidLoad里的所有同步代码)后,才会去处理MainActor队列里的那个Task任务,所以你会看到循环的所有打印都先输出,之后才轮到Task里的内容。
你可以做个小实验验证:把循环次数改成10次,再在循环里加个微小延迟,让主线程有空隙处理队列里的Task,你会发现Task的打印可能穿插在循环中间:
for i in 1...10 { print("Thread \(Thread.current)") Thread.sleep(forTimeInterval: 0.1) // 给主线程留出处理Task的空隙 }
最后再梳理一遍核心点
- 普通
Task {}继承当前Actor上下文,主线程创建的Task默认在主线程执行同步代码,直到遇到await挂起。 - 异步任务不会打断当前的同步执行流程,必须等当前同步代码执行完毕,主线程才会处理队列里的Task任务。
内容的提问来源于stack exchange,提问作者the monk

