1.截至目前群里的成員已經對skynet中的timeout提出了更多的要求。目前skynet提供的定時器是倒計時形式,且定時器一旦設置后,便不能撤銷(至少目前的實現是這樣),然后調用 cb
最近有人提出希望能支持一下撤銷定時器的功能,但雲風堅持:“框架只應該提供必不可少的特性,能用已有的特性實現的東西都應該刪掉”。
2.這里為什么說偽取消定時器呢?
skynet中當調用 skynet.timeout(time, cb)以后,便進入skynet_timer.c中管理,然后到時以后,將到時消息放到調用服務的消息隊列上,等待回調函數處理。
真正的取消定時器是黨調用取消定時器的接口后,停止倒計時同時將本次任務從某個地方徹底刪除,好像什么事都沒發生一樣。
那么偽取消就是倒計時繼續執行,只是當倒計時結束以后回調函數執行時,執行的不是調用定時器時注冊的回調,而是更改了的回調,這個回調不會做任何事情,由此來實現偽取消。
3.偽取消定時器的思路
首先看下skynet.timeout()。
function skynet.timeout(ti, func) local session = c.intcommand("TIMEOUT",ti) assert(session) local co = co_create(func) assert(session_id_coroutine[session] == nil) session_id_coroutine[session] = co return session end
1) 用"TIMEOUT"命令啟動本次倒計時,並返回一個session,然后用回掉函數創建一個協程,創建協程代碼如下:
local function co_create(f) local co = table.remove(coroutine_pool) if co == nil then co = coroutine.create(function(...) f(...) while true do f = nil coroutine_pool[#coroutine_pool+1] = co f = coroutine_yield "EXIT" f(coroutine_yield()) end end) else coroutine_resume(co, f) end return co end
用於本虛擬機中的協程池,coroutine_pool只會在這個函數使用,即如果池子里為空,則用 f 創建新協程並放到池子里,若有協程重新注冊 回調函數為 f。
2)用session_id_coroutine[session] = co來保存session與co的對應關系,當倒計時結束后會根據co來得到session。timeout函數到此就結束了,就這樣然后就看可以利用框架來實現倒計時結束后調用回調。看似也沒做什么,真正的奧秘在
local session = c.intcommand("TIMEOUT",ti).
手里有源碼的可以直接跟進去,看一下到底做了什么.
那么調用回調的地方在哪里呢?在 skynet.draw_dispatch_message(...)中。
function skynet.dispatch_message(...) local succ, err = pcall(raw_dispatch_message,...) while true do local key,co = next(fork_queue) if co == nil then break end fork_queue[key] = nil local fork_succ, fork_err = pcall(suspend,co,coroutine_resume(co)) if not fork_succ then if succ then succ = false err = tostring(fork_err) else err = tostring(err) .. "\n" .. tostring(fork_err) end end end assert(succ, tostring(err)) end local function raw_dispatch_message(prototype, msg, sz, session, source) -- skynet.PTYPE_RESPONSE = 1, read skynet.h if prototype == 1 then local co = session_id_coroutine[session] if co == "BREAK" then session_id_coroutine[session] = nil elseif co == nil then print("prototype, msg, sz, session, source: ", prototype, msg, sz, session, source) unknown_response(session, source, msg, sz) else session_id_coroutine[session] = nil suspend(co, coroutine_resume(co, true, msg, sz)) end else local p = proto[prototype] if p == nil then if session ~= 0 then c.send(source, skynet.PTYPE_ERROR, session, "") else unknown_request(session, source, msg, sz, prototype) end return end local f = p.dispatch if f then local ref = watching_service[source] if ref then watching_service[source] = ref + 1 else watching_service[source] = 1 end local co = co_create(f) session_coroutine_id[co] = session session_coroutine_address[co] = source suspend(co, coroutine_resume(co, session,source, p.unpack(msg,sz))) else unknown_request(session, source, msg, sz, proto[prototype].name) end end end
倒計時結束后會執行標黃代碼的邏輯:
session_id_coroutine[session] = nil
suspend(co, coroutine_resume(co, true, msg, sz))
這時根據 session 得到co,這個co便是回調的協程。
前面講到,為取消就是將這個co在為到時之前替換成什么都不執行的一個co。而替換的關鍵:1.幾下第一次timeout時desession;2,重新生成一個co。這兩個點都可以在skynet.lua中實現,代碼如下:
local function remove_timeout_cb(...) end function skynet.remove_timeout(session) local co = co_create(remove_timeout_cb) assert(session_id_coroutine[session] ~= nil) session_id_coroutine[session] = co end function skynet.timeout(ti, func) local session = c.intcommand("TIMEOUT",ti) assert(session) local co = co_create(func) assert(session_id_coroutine[session] == nil) session_id_coroutine[session] = co return session end
可以看到,增加了skynet.remove_timeout和skynet.remove_timeout_cb(),同時 skynet.timeout()中返回了獲得的session。代碼很簡單,就是照着前面的思路實現的。
4.測試代碼:
local session = skynet.timeout(1000, function() print("test timeout 10") end) skynet.remove_timeout(session) skynet.timeout(1500, function() print("test timeout 15") end)
5.測試過程:
1)修改skynet.lua,增加skynet.remove_timeout和skynet.remove_timeout_cb(),同時使skynet.timeout返回產生的session。
2)修改配置文件config,start = "testtimer" 將start的腳本改為test下的testtimmer,測試將在那里進行。
3)在testtimer.lua中的skynet.start中使用上邊三局測試代碼,或者自己編寫,便可得到結果。
到此,一個skyent的偽取消定時器實現了。當然可以通過向類似c.Initcommand("TIMEOUT", t1)這中形式,自己實現"REMOVE_TIMEOUT"命令,只是這樣更改的地方較多,但是可以實現徹底取消。
今天實現了臨時的取消,正着手創建新命令來徹底取消。特此記錄,歡迎指正與指教。