1.介绍
协程锁是异步编程时访问同一个变量,访问公共的资源(全局变量),就需要加锁,才能保证数据访问的安全。
2.原理(CoroutineLockComponent.cs)
1.Awake
生成各种类型的协程锁队列添加到list里面
public override void Awake(CoroutineLockComponent self)
{
CoroutineLockComponent.Instance = self;
self.list = new List<CoroutineLockQueueType>(CoroutineLockType.Max);
for (int i = 0; i < CoroutineLockType.Max; ++i)
{
//生成各种类型的协程锁队列添加到list里面
CoroutineLockQueueType coroutineLockQueueType = self.AddChildWithId<CoroutineLockQueueType>(++self.idGenerator);
self.list.Add(coroutineLockQueueType);
}
}
2.Wait
等待协程锁
public static async ETTask<CoroutineLock> Wait(this CoroutineLockComponent self, int coroutineLockType, long key, int time = 60000)
{
//获取对应类型的协程锁队列
CoroutineLockQueueType coroutineLockQueueType = self.list[coroutineLockType];
if (!coroutineLockQueueType.TryGetValue(key, out CoroutineLockQueue queue))
{
//如果没有key的对应队列则直接创建锁和队列
coroutineLockQueueType.Add(key, self.AddChildWithId<CoroutineLockQueue>(++self.idGenerator, true));
return self.CreateCoroutineLock(coroutineLockType, key, time, 1);
}
//如果有锁队列则挂起等待锁释放
ETTask<CoroutineLock> tcs = ETTask<CoroutineLock>.Create(true);
queue.Add(tcs, time);
return await tcs;
}
3.CreateCoroutineLock/AddTimer
创建协程锁和加入计时列表
private static CoroutineLock CreateCoroutineLock(this CoroutineLockComponent self, int coroutineLockType, long key, int time, int level)
{
//创建协程锁
CoroutineLock coroutineLock = self.AddChildWithId<CoroutineLock, int, long, int>(++self.idGenerator, coroutineLockType, key, level, true);
if (time > 0)
{
//加入超时队列
self.AddTimer(TimeHelper.ClientFrameTime() + time, coroutineLock);
}
return coroutineLock;
}
private static void AddTimer(this CoroutineLockComponent self, long tillTime, CoroutineLock coroutineLock)
{
//添加进timers列表并设置minTime
self.timers.Add(tillTime, new CoroutineLockTimer(coroutineLock));
if (tillTime < self.minTime)
{
self.minTime = tillTime;
}
}
4.TimeoutCheck/Update/RunNextCoroutine
每帧检测超时的锁并压入nextFrameRun队列,然后通知释放锁
public override void Update(CoroutineLockComponent self)
{
// 检测超时的CoroutineLock
TimeoutCheck(self);
// 循环过程中会有对象继续加入队列
while(self.nextFrameRun.Count > 0)
{
(int coroutineLockType, long key, int count) = self.nextFrameRun.Dequeue();
self.Notify(coroutineLockType, key, count);
}
}
private void TimeoutCheck(CoroutineLockComponent self)
{
// 超时的锁
if (self.timers.Count == 0)
{
return;
}
long timeNow = TimeHelper.ClientFrameTime();
if (timeNow < self.minTime)
{
return;
}
//超时的压入timeOutIds队列
foreach (KeyValuePair<long, List<CoroutineLockTimer>> kv in self.timers)
{
long k = kv.Key;
if (k > timeNow)
{
self.minTime = k;
break;
}
self.timeOutIds.Enqueue(k);
}
self.timerOutTimer.Clear();
while (self.timeOutIds.Count > 0)
{
long time = self.timeOutIds.Dequeue();
foreach (CoroutineLockTimer coroutineLockTimer in self.timers[time])
{
self.timerOutTimer.Enqueue(coroutineLockTimer);
}
self.timers.Remove(time);
}
while (self.timerOutTimer.Count > 0)
{
CoroutineLockTimer coroutineLockTimer = self.timerOutTimer.Dequeue();
//coroutineLockTimer.CoroutineLock.InstanceId = 0的跳过即销毁的锁
if (coroutineLockTimer.CoroutineLockInstanceId != coroutineLockTimer.CoroutineLock.InstanceId)
{
continue;
}
CoroutineLock coroutineLock = coroutineLockTimer.CoroutineLock;
// 超时直接调用下一个锁
self.RunNextCoroutine(coroutineLock.coroutineLockType, coroutineLock.key, coroutineLock.level + 1);
coroutineLock.coroutineLockType = CoroutineLockType.None; // 上面调用了下一个, dispose不再调用
}
}
public static void RunNextCoroutine(this CoroutineLockComponent self, int coroutineLockType, long key, int level)
{
// 一个协程队列一帧处理超过100个,说明比较多了,打个warning,检查一下是否够正常
if (level == 100)
{
Log.Warning($"too much coroutine level: {coroutineLockType} {key}");
}
self.nextFrameRun.Enqueue((coroutineLockType, key, level));
}
5.Notify
释放锁
public static void Notify(this CoroutineLockComponent self, int coroutineLockType, long key, int level)
{
//获取协程锁队列
CoroutineLockQueueType coroutineLockQueueType = self.list[coroutineLockType];
if (!coroutineLockQueueType.TryGetValue(key, out CoroutineLockQueue queue))
{
return;
}
//没有则移除key
if (queue.Count == 0)
{
coroutineLockQueueType.Remove(key);
return;
}
//返回ETTask的await释放锁
CoroutineLockInfo coroutineLockInfo = queue.Dequeue();
coroutineLockInfo.Tcs.SetResult(self.CreateCoroutineLock(coroutineLockType, key, coroutineLockInfo.Time, level));
}
3.使用
1.mono层可使用using
//使用协程锁
using (await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, id % DBComponent.TaskCount))
{
//内容
IAsyncCursor<T> cursor = await self.GetCollection<T>(collection).FindAsync(d => d.Id == id);
return await cursor.FirstOrDefaultAsync();
}
2.热更程不能用using,需要手动dispose
//使用协程锁
CoroutineLock coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.Location, key);
//内容
//释放锁
coroutineLock.Dispose();