如何在多资产异步加载完成后,让函数正确调用onComplete回调
解决异步批量加载资产后触发回调的问题
方法一:用计数器跟踪加载进度(新手友好)
这种方式逻辑直观,容易理解,核心是用线程安全的计数器统计已完成的加载任务,当计数等于总任务数时触发回调。
修改后的代码:
public void Load(Action onComplete) { // 没有资产要加载时直接触发完成 if (_db.Count == 0) { onComplete?.Invoke(); return; } int completedCount = 0; int totalAssets = _db.Count; foreach (var kvp in _db) { var key = kvp.Key; var assetRef = kvp.Value; assetRef.LoadAssetAsync<TAsset>().Completed += handle => { if (handle.Status == AsyncOperationStatus.Succeeded) _loadedAssets.Add(key, handle.Result); // 用Interlocked保证多线程下计数准确 int current = Interlocked.Increment(ref completedCount); if (current == totalAssets) { onComplete?.Invoke(); } }; } }
关键点说明:
- 用
Interlocked.Increment而不是普通的completedCount++:因为异步回调可能在不同线程执行,普通递增不是线程安全的,会导致计数错误。 - 先判断空集合:避免无意义的循环,直接触发完成回调。
方法二:用Task.WhenAll简化异步逻辑(推荐)
如果你的项目支持async/await(Unity 2017.1及以上版本已支持),可以把每个加载操作转为Task,用Task.WhenAll等待全部完成,代码更简洁易读。
修改后的代码:
public async void Load(Action onComplete) { var loadTasks = new List<Task>(); foreach (var kvp in _db) { var key = kvp.Key; var assetRef = kvp.Value; // 把LoadAssetAsync转成Task loadTasks.Add(LoadAssetTask(assetRef, key)); } // 等待所有加载任务结束 await Task.WhenAll(loadTasks); // 全部完成后触发回调 onComplete?.Invoke(); } // 辅助方法:将AssetReference的异步加载转为Task private Task LoadAssetTask(AssetReference assetRef, string key) { var taskSource = new TaskCompletionSource<bool>(); assetRef.LoadAssetAsync<TAsset>().Completed += handle => { if (handle.Status == AsyncOperationStatus.Succeeded) _loadedAssets.Add(key, handle.Result); // 标记任务完成(不管成功失败) taskSource.SetResult(true); }; return taskSource.Task; }
关键点说明:
TaskCompletionSource用来把基于事件的异步操作(Completed事件)转换成Task,这样就能用await语法等待。async void适合作为入口方法或事件处理方法,如果是普通业务方法,建议用async Task返回任务,方便上层处理。
额外注意事项:
- 如果需要处理加载失败的情况,可以在回调里判断
handle.Status == AsyncOperationStatus.Failed,记录错误信息;方法二中也可以用taskSource.SetException抛出异常,上层用try/catch捕获。 - 如果
_loadedAssets是普通Dictionary,多线程下可能出现并发写入问题,建议换成ConcurrentDictionary保证线程安全。
内容的提问来源于stack exchange,提问作者WDUK
相关产品推荐
相关产品推荐

