协程(Coroutine)就是一段能自己中断和唤醒的程序片段,借助线程去理解,不同的是,它是由你来维护,看起来异步的逻辑,其实是同步顺序执行的,非常美妙。
接触过一段Unity开发,了解到C#里也有协程,但和之前Lua里的不太一样。C#只提供了一个yield关键字,借助原本就存在的枚举器(IEnumerator)实现的。yield return 将对象返回到枚举器的上层调用,同时保存枚举器的函数内状态,直到上层调用枚举器的下一个取值。本质上C#是为了简化代码,更容易实现一种枚举器的写法,而协程是附加产物。当然是可以简单封装一下,让协程的可中断特性浮现出来,Unity中的Coroutine,可以用于等待异步操作等,用起来很方便。
Lua的协程,提供了coroutine.yield和coroutine.resume,前者用于挂起,后者用于恢复执行,它们是可以相互传递参数的,就像一拉一推相互协助的两只手。那么问题来了,Lua能不能像C#那样玩枚举器?
(1)Lua的枚举器
没错就是for语句,是不是觉得写了这么多for ... ipairs/pairs,对for还是有些不太明白,总之有点复杂,反正pairs之类很好就够了?!
拿pairs分析一下:
pairs很简单,传入t一个table,返回next,t,nil。t还是那个t,关键在于next。单看next也很简单,next(t, k),t是一个table,k是某个table存在key,那么它返回key的下一个key和对应的value,初始状态是:next(t, nil)返回第一个key1,value1,最后一个next(t, keyn)返回nil。写这么多,这就是next啊,很准确的表达。
复杂的东西是由简单的元素构成的。
Lua文档上有这样的描述:
A for statement like
for var_1, ···, var_n in explist do block end
is equivalent to the code:
do
local f, s, var = explist
while true do
local var_1, ···, var_n = f(s, var)
var = var_1
if var == nil then break end
block
end
end
用next放上去,pairs是这样干的:
k1,v1 = next(t,nil)
k2,v2 = next(t,k1)
...
nil = next(t, kn)
枚举的过程中,next的第1个参数始终是in 后面的第2个参数,第2个参数是变化的每次枚举返回的第1个参数,枚举过程直到返回nil为止。
还是有点晕,我们不看next的传入,它的返回参数每次都枚举出来的值,所以我们可以简单用一个有状态的函数来枚举,对用闭包,比如:
local t = {"A", "B", "C", "D"}
local i = 0
local myipairs = function()
i = i + 1
return t[i] and i, t[i]
end
for i, v in myipairs do
print(i, v)
end
for语句的in后面的参数就是枚举器,你可以用一个无状态的枚举器,当然需要紧跟后面一个不变量(通常是要枚举的容器),再跟一个初始可变量(通常是某种关键值)构成。当然也可以用有状态的函数闭包,因为函数有状态,所以每次使用需要一个工厂函数重新生成,上面的例子不能再使用一次,通常会改成这样:
local t = {"A", "B", "C", "D"}
function myipairs(t)
local i = 0
return function()
i = i + 1
return t[i] and i, t[i]
end
end
for i, v in myipairs(t) do
print(i, v)
endfunction 里 return function,这很简单。
(2)用coroutine.yield实现枚举器
初步构思是想实现这样的用法:
function myipairs(t)
return function()
for i,v in ipairs(t) do
coroutine.yield(i, v)
end
end
end
local t = {"A", "B", "C", "D"}
function myipairs(t)
local co = coroutine.create(function()
for i,v in ipairs(t) do
coroutine.yield(i, v)
end
end)
return function()
local status, i, v = coroutine.resume(co)
if status then
return i, v
end
return nil
end
end
for i, v in myipairs(t) do
print(i, v)
end
它工作得很好。最后一个甜点:调用一次就会resume协程,同时如果成功就会返回resume的第2到最后参数,如果失败就会返回nil,Lua已经提供了这样的函数:coroutine.wrap,参看文档:
coroutine.wrap (f)
Creates a new coroutine, with body f. f must be a Lua function. Returns a function that resumes the coroutine each time it is called. Any arguments passed to the function behave as the
extra arguments to resume. Returns the same values returned by resume, except the first boolean. In case of error, propagates the error
最终进一步简化上面的代码,最后一段代码展示枚举数组中任意2个值的组合,以此作为结束,一起感受Lua的简洁和美吧。
local t = {"A", "B", "C", "D"}
function myipairs(t)
return coroutine.wrap(function()
for i,v1 in ipairs(t) do
for j,v2 in ipairs(t) do
if i < j then
coroutine.yield(v1, v2)
end
end
end
end)
end
for a, b in myipairs(t) do
print(a, b)
end
本文探讨了Lua中的协程及其与枚举器的关系,通过对比C#协程实现方式,介绍了Lua如何利用coroutine.yield和coroutine.resume实现枚举器功能,并给出具体示例。
1280

被折叠的 条评论
为什么被折叠?



