在 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
在 setup()
執行比較耗時的函數時, 能夠暫停掛起該函數, 跳轉到 draw()
往屏幕上輸出一些提示信息, 具體作法就是把該函數做爲任務加入線程類的任務列表, 而後在該函數最耗時的代碼位置處插入 switchPoint()
函數, 設置好時間片.線程
執行一些 http.request
或 socket
操做時, 爲避免長時間等待, 也能夠把這些操做做爲任務加入線程類的任務列表, 而後在該函數最耗時的代碼位置處插入 switchPoint()
函數, 設置好時間片,調試
須要輪流執行多個任務時, 能夠把全部任務都加入任務列表, 用它來調度.code
總之就是諸如此類的狀況均可以使用.orm