1. 程式人生 > >tolua移除協程的各種坑

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 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

希望以後不會再有問題了