async / await
async方法一定需要返回Task类型对象
public async Task MyAsyncMethod() { Console.WriteLine($"Async Method Start - Thread: {Thread.CurrentThread.ManagedThreadId}"); await Task.Delay(1000); // 模拟异步操作 Console.WriteLine($"Async Method End - Thread: {Thread.CurrentThread.ManagedThreadId}"); } public async Task MainMethod() { Console.WriteLine($"Main Method Start - Thread: {Thread.CurrentThread.ManagedThreadId}"); Task task = MyAsyncMethod(); // 调用async方法 // 调用async方法后、await之前的代码逻辑 Console.WriteLine($"After calling async method - Thread: {Thread.CurrentThread.ManagedThreadId}"); await task; // 等待异步方法完成 Console.WriteLine($"Main Method End - Thread: {Thread.CurrentThread.ManagedThreadId}"); }
执行顺序:caller调用async函数前的同步部分 - callee的同步部分 - callee await状态,返回Task对象 - caller调用async后的同步部分 - caller await,线程回收 / 切换线程 - callee异步部分执行完毕,执行剩余的方法 - caller执行剩余的方法
关于.NET中异步操作的线程分配:
- I/O 密集型任务(如文件读写、网络请求):
- 这些任务通常不需要占用线程,因为它们依赖于操作系统的异步I/O机制。
- 当await一个I/O操作时,当前线程不会被阻塞,而是立即释放回线程池,等待I/O操作完成。
- I/O操作完成后,操作系统会通知.NET,任务调度器会安排一个线程(可能是原来的线程,也可能是线程池中的其他线程)继续执行await之后的代码。
- CPU 密集型任务(如计算任务):
- 这些任务需要占用线程来执行。
- 如果await一个CPU密集型任务(例如Task.Run),任务会被分配到线程池中的一个线程执行。
- 当前线程会被释放,任务完成后,线程池中的某个线程会继续执行await之后的代码。
在Unity中,如果执行完异步任务后希望访问游戏对象、渲染相关的API,则需要手动切回去:
async void LoadAsync() { Debug.Log("主线程ID: " + Thread.CurrentThread.ManagedThreadId); await Task.Run(() => HeavyComputation()); // 在后台线程执行 await UnitySyncContext; // 切换回主线程 transform.position = new Vector3(0, 0, 0); // 需在主线程操作 }
底层原理: