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"命令,只是這樣更改的地方較多,但是可以實現徹底取消。

今天實現了臨時的取消,正着手創建新命令來徹底取消。特此記錄,歡迎指正與指教。

 

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM