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
希望以後不會再有問題了