一开始发现tolua没有停止协程的功能,而lua协程也不支持从外部停止,于是就想自己封装一层。最开始写了一个最简单的stop接口:
function coroutine.stop(co) print("stopCo1", co, debug.traceback()) local timer = comap[co] print("stopCo2", timer) if timer ~= nil then comap[co] = nil timer:Stop() print(timer, timer.running) end end
看上去似乎没有任何问题,通过对定时器的暂停,这样子协程就不会跑了。
对了,comap是我自己加的缓存协程和定时器的字典。
但后来发现一个问题,就是协程突然不执行了。
经过各种排查,发现是复用引起的。在coroutine.step调用结束之后,定时器被Stop了,而且被回收到了对象池,
但是compa[co]并没有清空,导致定时器被别人使用的时候,突然来了一个stop把那个其实不属于自己的定时器停止了。
于是通过修改coroutine.step函数修复了这个bug:
local action = function() local flag, msg = resume(co, unpack(args)) timer.func = nil table.insert(pool, timer) comap[co] = nil timer:Stop() if not flag then error(debug.traceback(co, msg)) return end end
后来又出现了一个bug,协程又他妈不执行了。经过仔细的排查,发现是这样的一个流程导致的:
local action = function() local flag, msg = resume(co, unpack(args)) 执行resume里面又调用了coroutine.step,导致comap[co]里面的timer其实已经 不是上一个协程的timer 如果你继续执行comap[co],就会导致那个正在执行的timer反而被清空。 timer.func = nil table.insert(pool, timer) comap[co] = nil timer:Stop() if not flag then error(debug.traceback(co, msg)) return end end
于是增加了一个判断:
local action = function() local flag, msg = resume(co, unpack(args)) timer.func = nil table.insert(pool, timer) if timer == comap[co] then comap[co] = nil end timer:Stop() if not flag then error(debug.traceback(co, msg)) return end end
(PS:这里请假了tolua作者蒙哥,其实是我的时序出了问题,要在resume之前清除字典索引,应该这么写:
local action = function()
comap[co] = nil
timer.func = nil
timer:Stop()
local flag, msg = resume(co, unpack(args)) table.insert(pool, timer) if not flag then error(debug.traceback(co, msg)) return end end
)
本来以为这样已经完美,可新问题出现了,协程无法被停止了,经过仔细的排查,发现是这样的。
function FrameTimer:Start() if not self.handle then self.handle = CoUpdateBeat:CreateListener(self.Update, self) end if self.running then print("runing twice", self) else print(self, debug.traceback()) end CoUpdateBeat:AddListener(self.handle) print("lock1", CoUpdateBeat.lock) self.running = true end
协程的定时器启动的时候,CoUpdateBeat正在执行,tolua是这么处理的:
function _event:AddListener(handle) assert(handle) if self.lock then table.insert(self.opList, function() self.list:pushnode(handle) end) else self.list:pushnode(handle) end end
对于正在执行的情况下,把将要加入队列的handle放到opList中,在当前队列执行完毕后,去执行opList操作,把handle放入到队列中。
如下:
_event.__call = function(self, ...) local _list = self.list self.lock = true local ilist = ilist for i, f in ilist(_list) do self.current = i local flag, msg = f(...) if not flag then if self.keepSafe then _list:remove(i) end self.lock = false error(msg) end end local opList = self.opList self.opList = {} self.lock = false for _, op in ipairs(opList) do op() end end
但我使用的时候出现了一种情况,导致无法停止协程。
首先,我启动了一个协程,定时器被AddListener,此时刚好UpdateBeat正在被执行,于是lock为true,这个操作被放到opList中,而并没有真正的加入到队列中。直到执行了op()之后,才被放入到队列中。所以目前的状况是,协程在队列中,但还没有被执行。
然后,下一个Update来临,协程对列正在被执行,但刚好还没执行到我启动的那个协程,就在这时,我调用了stop函数,想要停止这个协程,由于协程队列在执行,所以我的这个remove请求会被放到opList中,所以只有等到协程执行完毕之后,我的remove才会被执行。于是这个协程其实没有被停止。
这其实就是我们平时经常碰到的问题,c#遍历一个字典的时候,你是无法移除里面的东西的,你只能设置一个标志。于是我也打算这么解决这个问题。如果tiemr的标志running为false,那么即时协程执行到了,就直接return.
function FrameTimer:Update() if Time.frameCount >= self.count then print(self, self.running) if not self.running then return end self.func() if self.loop > 0 then self.loop = self.loop - 1 end if self.loop == 0 then self:Stop() else self.count = Time.frameCount + self.duration end end end
希望以后不会再有问题了