用 Lua 的协程 coroutine 在 Codea 上实现一个多任务调度类

用 Lua 的协程 coroutine 在 Codea 上实现一个多任务调度类

概述

问题描述

Codea 中调试程序时发现一个问题: 如果在 setup() 中执行了比较耗时的语句, 比如地图生成, 资源下载等操作, 那么在该这些操作没有完成之前屏幕上是不会显示任何内容的, 你只能傻傻地等它完成, 如果是调试版本还可以通过 print 在侧面的调试窗口打印一些信息, 如果是正式版本就不太适合调出调试窗口了, 怎么办呢?

解决办法

于是一边学习研究 Lua 的协程 coroutine, 一边写了一个多任务调度类, 该类有如下特点特点:

  • 可自由设置全局性的时间片
  • 可针对每个任务设置不同的时间片(理论上可行, 尚未实际验证)
  • 可自由添加不同任务

也可以把它看做一个线程类, 用它来控制流程就可以解决上面遇到的问题.

多任务调度类

类代码

类代码如下:

--# 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 
```	

具体代码就不多解释了, 基本上每行都有注释.

###	测试代码

可用如下的主程序框架来测试:

  • 主程序框架 function setup() print("thread...")

    myT = Threads() myT.timeTick = 1/2 myT:addTaskToList(tt) myT:addTaskToList(oo) myT:addTaskToList(mf) myT:addTaskToList(pk)

    --[[ myT.taskList2 --]]

    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.request` 或 `socket` 操作时, 为避免长时间等待, 也可以把这些操作作为任务加入线程类的任务列表, 然后在该函数最耗时的代码位置处插入 `switchPoint()` 函数, 设置好时间片, 

-	场景3 

需要轮流执行多个任务时, 可以把所有任务都加入任务列表, 用它来调度.

总之就是诸如此类的情况都可以使用.

转载于:https://my.oschina.net/freeblues/blog/692593

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值