Unity异步任务为何在测试2中使用主线程外的其他线程?
Unity异步任务线程差异原因解析
问题背景
在Unity 2021.3.11f1(API兼容级别为.NET Framework)中调试异步任务时,发现两种不同await写法的线程使用存在明显差异:Test1直接await异步方法全程在主线程执行,Test2通过Task.Factory.StartNew包裹异步方法后,却使用了主线程外的ThreadPool线程,且await前后线程ID发生变化。
测试结果
Test1:直接await SimpleTaskAsync
输出:
- Task started on thread: 1
- SimpleTask before delay on thread: 1
- SimpleDelay on thread: 1
- SimpleTask after delay on thread: 1
Test2:await Task.Factory.StartNew(async () => await SimpleTaskAsync())
输出:
- Task started on thread: 1
- SimpleTask before delay on thread: 25
- SimpleDelay on thread: 25
- SimpleTask after delay on thread: 26
核心原因解析
1. Unity的同步上下文(SynchronizationContext)
Unity对async/await做了特殊适配,主线程运行时会使用UnitySynchronizationContext。当在主线程中await任务时,该同步上下文会被捕获,await之后的代码会被调度回主线程执行,这是Test1全程主线程的核心原因:
Start方法在主线程执行,awaitSimpleTaskAsync时捕获Unity同步上下文;SimpleTaskAsync里的await Task.Delay同样捕获该上下文,延迟完成后回到主线程执行后续代码。
2. Task.Factory.StartNew的行为特性
Task.Factory.StartNew默认使用ThreadPoolTaskScheduler,会把任务调度到ThreadPool线程执行,且它不识别async lambda:
- 外层的
Task.Factory.StartNew(async () => ...)会创建一个运行在ThreadPool线程(线程25)的任务; - 在这个ThreadPool线程中调用
SimpleTaskAsync时,当前没有Unity同步上下文(ThreadPool线程默认无自定义同步上下文); - 当
await Task.Delay执行时,因为没有捕获到Unity同步上下文,会使用默认的ThreadPool上下文,延迟完成后会被调度到任意可用的ThreadPool线程(线程26),而非主线程。
3. 隐性因素:async lambda的嵌套任务
Task.Factory.StartNew(async () => ...)实际返回的是Task<Task>而非Task。直接await它时,会先等待外层任务完成(进入async lambda的第一个await点),再等待内层的SimpleTaskAsync完成,这进一步放大了线程切换的表现,但核心还是同步上下文的差异。
完整测试代码
using System.Threading; using System.Threading.Tasks; using UnityEngine; public class TestAsyncTask : MonoBehaviour { private async void Start() { Debug.Log($"Task started on thread: {Thread.CurrentThread.ManagedThreadId}"); // TEST 1 await SimpleTaskAsync(); // TEST 2 await Task.Factory.StartNew(async () => await SimpleTaskAsync()); } private async Task SimpleTaskAsync() { Debug.Log($"SimpleTask before delay on thread: {Thread.CurrentThread.ManagedThreadId}"); await SimpleDelayAsync(); Debug.Log($"SimpleTask after delay on thread: {Thread.CurrentThread.ManagedThreadId}"); } private async Task SimpleDelayAsync() { Debug.Log($"SimpleDelay on thread: {Thread.CurrentThread.ManagedThreadId}"); await Task.Delay(1000); } }
内容的提问来源于stack exchange,提问作者ge.go
相关产品推荐
相关产品推荐

