首页 > 代码库 > skynet之伪取消定时器

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 sessionend

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 coend

用于本虚拟机中的协程池,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))endlocal 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    endend

倒计时结束后会执行标黄代码的逻辑:

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(...)endfunction skynet.remove_timeout(session)    local co = co_create(remove_timeout_cb)    assert(session_id_coroutine[session] ~= nil)    session_id_coroutine[session] = coendfunction 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 sessionend

可以看到,增加了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"命令,只是这样更改的地方较多,但是可以实现彻底取消。

今天实现了临时的取消,正着手创建新命令来彻底取消。特此记录,欢迎指正与指教。

 

 

 

  

 

skynet之伪取消定时器