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

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

      

  • 相关阅读:
    2015.2.27 UltraEdit中显示XML结构
    2015.1.31 DataGridView自动滚动到某行
    2015.1.15 利用函数实现将一行记录拆分成多行记录 (多年想要的效果)
    2015.1.15 利用Oracle函数返回表结果 重大技术进步!
    2015.1.15 利用Oracle函数插入表结构 Bulk collect into 不用循环,简洁高效
    2015.1.8 Left join 左连接
    2015.1.10 解决DataGridView SelectionChanged事件自动触发问题
    delphi 遍历窗口
    delphi 访问 protected 属性 哈哈
    clientdataset 读取excel 如果excel 文件不存在的时候 相应的gird 会不显示数据, 鼠标掠过 gird 格子 才会显示数据。 这是一个bug 哈哈
  • 原文地址:https://www.cnblogs.com/newbeeyu/p/5874149.html
Copyright © 2011-2022 走看看