2018年6月27日星期三

tolua移除协程的各种坑

一开始发现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 flagmsg = resume(counpack(args)) table.insert(pooltimer) if not flag then error(debug.traceback(comsg)) 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

希望以后不会再有问题了

没有评论:

发表评论