skynet之伪取消定时器
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"命令,只是这样更改的地方较多,但是可以实现彻底取消。
今天实现了临时的取消,正着手创建新命令来彻底取消。特此记录,欢迎指正与指教。