Skip to main content

异步编程

img

在C#中,await 关键字用于异步编程,它允许你等待一个异步操作(例如网络请求或长时间运行的计算任务)的完成,而不会阻塞主线程的执行。await 只能在标记为 async 的方法内部使用,并且只能与返回 Task 或 Task<TResult> 的方法一起使用。

异步编程核心原理图示

1. 同步/异步执行流程对比

img

关键区别:异步编程通过线程池分流耗时操作,避免主线程阻塞(尤其在Unity中需保持60FPS渲染)

2. async/await状态机原理

img

编译器会将async方法转换为实现IAsyncStateMachine接口的状态机类,管理await断点与恢复


Unity中5大异步实现方式

1. async/await(推荐方案)

// Unity资源加载示例
public async Task<GameObject> LoadAssetAsync(string path) {
ResourceRequest request = Resources.LoadAsync<GameObject>(path);
await request; // 等待异步加载完成
return request.asset as GameObject;
}

// 调用示例
async void Start() {
var prefab = await LoadAssetAsync("Enemies/Dragon");
Instantiate(prefab);
}

优势:代码简洁如同步,自动处理线程切换

2. Coroutine(协程)与异步混用

img

IEnumerator LoadSceneCoroutine() {
var webRequest = UnityWebRequest.Get("https://api.gamedata");
yield return webRequest.SendWebRequest(); // 传统Yield
var data = JsonUtility.FromJson<GameData>(webRequest.downloadHandler.text);

// 与await混用
await LoadAssetsAsync(data.assetList);
}

适用场景:兼容旧代码或需要与WWW/UnityWebRequest配合

3. Task.Run(CPU密集型计算)

public async Task<int> CalculateDamageAsync() {
return await Task.Run(() => {
// 复杂伤害计算(避免卡主线程)
int result = 0;
for (int i = 0; i < 1000000; i++) {
result += i % 10;
}
return result;
});
}

注意:Unity部分API(如Transform)需回到主线程执行

4. UniTask(第三方优化方案)

// 避免GC分配的传统Task
using Cysharp.Threading.Tasks;

async UniTaskVoid LoadAllAssets() {
await UniTask.WhenAll(
LoadPrefabAsync("Player"),
LoadSceneAsync("Level1")
);
Debug.Log("All loaded!");
}

优势:零GC分配、支持取消操作,专为Unity优化

5. EAP模式(旧版网络请求)

void DownloadFile(string url) {
WebClient client = new WebClient();
client.DownloadFileCompleted += (sender, e) => {
// 回到主线程执行
UnityMainThreadDispatcher.Instance.Enqueue(() => {
Debug.Log("Download complete!");
});
};
client.DownloadFileAsync(new Uri(url), "localPath");
}

现状:已被TAP模式(async/await)取代,但需了解。


Unity特化面试问题与优化策略

异步加载资源时如何避免"MissingReferenceException"?

解决方案

使用MonoBehaviour生命周期检测

if (this == null) return; // 对象已销毁

通过CancellationToken取消任务

var cts = new CancellationTokenSource();
void OnDestroy() {
cts.Cancel();
}
await LoadAssetAsync("path", cts.Token);

通过GameObject.activeInHierarchy检查对象有效性

如何优化高频异步调用(如伤害计算)?

性能优化方案

  1. 对象池+缓存Task
static Dictionary<string, Task<GameObject>> _prefabCache = new();

public static Task<GameObject> GetPrefabAsync(string path) {
if (!_prefabCache.TryGetValue(path, out var task)) {
task = Resources.LoadAsync<GameObject>(path).AsTask();
_prefabCache[path] = task;
}
return task;
}
  1. 使用ValueTask减少堆分配
public async ValueTask<int> OptimizedCalculation() {
if (_cachedResult != null)
return _cachedResult.Value;
return await Task.Run(...);
}

async/await会创建新线程吗?

  • I/O操作(如网络请求):不创建线程,使用操作系统回调机制
  • CPU密集型任务Task.Run会使用线程池线程
  • Unity主线程:await默认回到原同步上下文(主线程)

异步编程性能优化方案

img


异步编程禁忌与最佳实践

1. Unity主线程限制

img

解决方案:通过UnitySynchronizationContext回到主线程

await Task.Run(() => {
// 后台线程计算
}).ContinueWith(t => {
// 自动切换回主线程
}, TaskScheduler.FromCurrentSynchronizationContext());

2. 异常处理模板

async void SafeAsyncMethod() {
try {
var result = await RiskyOperation();
}
catch (UnityWebRequestException ex) {
Debug.LogError($"网络错误: {ex.ResponseCode}");
}
catch (OperationCanceledException) {
Debug.Log("任务已取消");
}
finally {
// 资源清理
}
}
  1. 帧率敏感场景优化

img

详情可以看《Unity 客户端面试宝典》中 Unity 游戏引擎 - 优化策略 - 分帧处理

img

  1. 调试技巧
  • 使用Debug.Log($"Thread: {Thread.CurrentThread.ManagedThreadId}")查看线程切换
  • Unity Profiler中观察Task对象分配情况