在 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