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

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

 

 

 

  

 

posted @ 2016-09-15 01:41  mr_yu  阅读(2994)  评论(3编辑  收藏  举报