zoukankan      html  css  js  c++  java
  • 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) .. "
    " .. 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"命令,只是这样更改的地方较多,但是可以实现彻底取消。

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

      

  • 相关阅读:
    Linux基礎命令
    Linux_文件系統結構
    Linux_目錄結構與操作_基本命令
    JS简单打字小游戏demo
    开发板通过路由器访问外网
    VIM基本操作命令表
    破解source insight4.0
    进程控制
    静态库与动态库的制作和使用
    STM32建工程模板
  • 原文地址:https://www.cnblogs.com/newbeeyu/p/5874149.html
Copyright © 2011-2022 走看看