用 Lua 的協程 coroutine 在 Codea 上實現一個多任務調度類

用 Lua 的協程 coroutine 在 Codea 上實現一個多任務調度類

概述

問題描述

Codea 中調試程序時發現一個問題: 若是在 setup() 中執行了比較耗時的語句, 好比地圖生成, 資源下載等操做, 那麼在該這些操做沒有完成以前屏幕上是不會顯示任何內容的, 你只能傻傻地等它完成, 若是是調試版本還能夠經過 print 在側面的調試窗口打印一些信息, 若是是正式版本就不太適合調出調試窗口了, 怎麼辦呢?框架

解決辦法

因而一邊學習研究 Lua 的協程 coroutine, 一邊寫了一個多任務調度類, 該類有以下特色特色:socket

  • 可自由設置全局性的時間片
  • 可針對每一個任務設置不一樣的時間片(理論上可行, 還沒有實際驗證)
  • 可自由添加不一樣任務

也能夠把它看作一個線程類, 用它來控制流程就能夠解決上面遇到的問題.函數

多任務調度類

類代碼

類代碼以下:學習

--# Threads
-- 最新版本, 可自由增長多個不一樣任務

Threads = class()

function Threads:init()
    self.threads = {}    
    self.taskList = {}
    self.time = os.clock()   
    self.timeTick = 0.01
    self.taskID = 1
    self.taskStatus = {}
    self.taskVT = {}
    self.img = image(100,100)
end

-- 設置任務函數,插入任務列表
function Threads:addTaskToList(task)
    local t = function() task() end
    table.insert(self.taskList, t)
end

-- 爲全部任務建立對應的協程,該函數執行一次便可。
function Threads:job()    
    -- 爲任務列表中的全部任務函數,都建立對應的協程,並插入 self.threads 表中
    local n = #self.taskList
    for id = 1, n do
        -- local f = function () self.taskList[id]() end
        local f = function () self:taskUnit(id) end
        -- 爲 taskUnit() 函數建立協程。
        local co = coroutine.create(f)
        table.insert(self.threads, co)   
        -- 記錄全部任務的狀態,此時應爲 suspended
        self.taskStatus[id] = coroutine.status(co)
    end
end

-- 任務單元,要在本函數中設置好掛起條件
function Threads:taskUnit(id)
	-- 可在此處執行用戶的任務函數
    -- self.task()
    self.taskID = id
    self.taskList[id]()
    
	-- 切換點, 放在 self.task() 函數內部耗時較長的位置處, 以方便暫停
    -- self:switchPoint(id)      
	
	-- 運行到此說明任務所有完成, 設置狀態 
    -- self.taskStatus[id] = "Finished" 
end

-- 切換點, 可放在準備暫停的函數內部, 通常選擇放在多重循環的最裏層, 這裏耗時最多
function Threads:switchPoint(id)
    -- 切換線程,時間片耗盡,而工做尚未完成,掛起本線程,自動保存現場。
    if (os.clock() - self.time) >= self.timeTick then   
        -- 查看調試信息,儘可能放在這裏,尤爲是 print 函數,不要放在任務函數內部
        print("hello: No."..id.." is "..self.taskStatus[id])  
        -- self:visual(id)
        -- 重置任務時間
        self.time = os.clock()  
        -- 掛起當前協程 
        coroutine.yield()    
    end
end

function Threads:visual(id)
    local n = #self.taskList
    local vt = {}
    background(18, 16, 16, 255)
    setContext(self.img)
    pushStyle()
    strokeWidth(1)
    fill(255, 211, 0, 255)
    -- if self.taskID == 1 then fill(241, 7, 7, 255) else fill(255, 211, 0, 255) end
    local w,h = self.img.width/n, self.img.height/n
    local x,y = 0,0
    for i = 1, n do
        vt[i] = function () rect(100+x+(i-1)*w,100+y+(i-1)*h,w,h) end
    end    
    popStyle()
    setContext()
    -- sprite(self.img,300,300)
    -- vt[self.taskID]()
    print("id: "..id)
    vt[id]()
end

-- 在 draw 中運行的分發器,借用 draw 的循環運行機制,調度全部線程的運行。
function Threads:dispatch()
    local n = #self.threads
    -- 線程表空了, 表示沒有線程須要工做了。
    if n == 0 then return end   
    for i = 1, n do
    	-- 記錄哪一個線程在工做。
        self.taskID = i    
        -- 恢復"coroutine"工做。
        local status = coroutine.resume(self.threads[i])
        -- 記錄任務狀態
        self.taskStatus[i] = coroutine.status(self.threads[i])
        -- 線程是否完成了他的工做?"coroutine"完成任務時,status是"false"。
        -- 若完成則將該線程從調度表中刪除, 將對應任務從任務列表刪除,同時返回。
        if not status then
            self.taskStatus[i] = "Finished" 
            table.remove(self.threads, i)
            -- table.remove(self.taskList,i)
            return
        end
    end
end

具體代碼就很少解釋了, 基本上每行都有註釋.測試

測試代碼

可用以下的主程序框架來測試:spa

- 主程序框架
function setup()
    print("thread...")
    
    myT = Threads()
    myT.timeTick = 1/2
    myT:addTaskToList(tt)
    myT:addTaskToList(oo)
    myT:addTaskToList(mf)
    myT:addTaskToList(pk)

    --[[ myT.taskList[2]()
    --]]

    myT:job()
    
    print(unpack(myT.taskList))
        
end

function draw()
    background(0)
    -- sprite("Documents:3D-Wall", WIDTH/2,HEIGHT/2)
    myT:dispatch()
    fill(244, 27, 27, 255)
    print("2: "..myT.taskStatus[1])
    print("length: "..#myT.taskList)
    -- local per = string.format("Worker %d calculating, %f%%.", p, (k / to * 100))
    -- text(per,300,300)
    sysInfo()
end

function tt ()
    while true do
        -- print("tt: "..os.clock())
        myT:switchPoint(myT.taskID)
    end
end

function oo ()
    while true do
        -- print("oo: "..os.clock())
        myT:switchPoint(myT.taskID)
    end
end

function mf ()
    local k = 0
    for i=1,10000000 do
        k = k + i
        -- print("mf: "..k)
        -- 若是運行時間超過 timeTick 秒, 則暫停
        myT:switchPoint(myT.taskID)
    end
end

function pk ()
    local k = 0
    for i=1,10000000 do
        k = k + i
        -- print("pk: "..k)
        -- 若是運行時間超過 timeTick 秒, 則暫停
        myT:switchPoint(myT.taskID)
    end
end

應用場景

  • 場景1

setup() 執行比較耗時的函數時, 能夠暫停掛起該函數, 跳轉到 draw() 往屏幕上輸出一些提示信息, 具體作法就是把該函數做爲任務加入線程類的任務列表, 而後在該函數最耗時的代碼位置處插入 switchPoint() 函數, 設置好時間片.線程

  • 場景2

執行一些 http.requestsocket 操做時, 爲避免長時間等待, 也能夠把這些操做做爲任務加入線程類的任務列表, 而後在該函數最耗時的代碼位置處插入 switchPoint() 函數, 設置好時間片,調試

  • 場景3

須要輪流執行多個任務時, 能夠把全部任務都加入任務列表, 用它來調度.code

總之就是諸如此類的狀況均可以使用.orm

相關文章
相關標籤/搜索