用 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

相关文章
相关标签/搜索