一、ArgumentException
参数不合法,比如对一个字典重复的添加key,比如一个方法期望的参数是string,结果传来一个null。
二、需求场景
异步场景下对同一个 Dictionary 重复 Add 同一 key 的竞态问题与解决。
比如我有一个Dictionary<string,Pool> pools,存储对象池。
然后我需要对一个资源进行中GetOrCreatePool,pools有就直接返回,没有就创建后返回。
那么当2个异步S1和S2操作同时执行:
// 任意一个地方代码执行了
await S1();
// 同时另一个对象的一个代码执行了
await S2();
// 现在S1和S2两个异步同时开启
public async UniTask S1()
{
await 加载资源A; // 耗时操作
AddToDic();
}
public async UniTask S2()
{
await 加载资源A; // 耗时操作
AddToDic();
}
private void AddToDic()
{
// 向pool添加 ["A",资源A]
}
整个流程是:
S1方法开始运行。
S2方法开始运行。
S1加载资源A。
S2加载资源A。
S1加载完毕,向字典添加。
S2加载资源完毕,向字典添加。
此时发现,ArgumentException。
三、解决
加载资源前进行一次检查,如果有就不需要再加载资源了。
加载资源后进行一次检查,如果有就不创建池了。
public async UniTask<LeanGameObjectPool> GetOrCreatePoolAsync(
string key,
Func<UniTask<GameObject>> prefabLoader,
string poolName = null,
int preloadCount = 0,
int capacity = 0,
bool recycle = false)
{
Debug.Log("GetOrCreatePoolAsync Key:" + key);
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Pool key is null or empty");
// // 第一次快速检查
if (_pools.TryGetValue(key, out var existingPool))
{
return existingPool;
}
// 真正去加载 Prefab(这一步可能会耗时,不能加锁)
var prefab = await prefabLoader();
if (prefab == null)
{
throw new NullReferenceException($"GetOrCreatePoolAsync prefab is null, key = {key}");
}
poolName ??= key;
// 二次检查 + 创建
// 有可能别的协程在我们等待 prefab 的时候已经创建好这个 pool 了
if (_pools.TryGetValue(key, out existingPool))
{
return existingPool;
}
var pool = LeanPoolCreateUtils.CreateLeanPool(
poolName,
prefab,
preloadCount,
poolsRoot,
capacity,
recycle);
_pools.Add(key, pool);
return pool;
}
这是一个简单的问题,但是是一个常犯的错误。
3417

被折叠的 条评论
为什么被折叠?



