协程和枚举器

协程(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文档上有这样的描述:

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)
end

function 里 return function,这很简单。


(2)用coroutine.yield实现枚举器

初步构思是想实现这样的用法:

function myipairs(t)
	return function()
		for i,v in ipairs(t) do
			coroutine.yield(i, v)
		end
	end
end


很明显是不能工作的,枚举器每次调用要返回一组值,正好是coroutine.yield的参数,所以枚举器应该是一次coroutine.resume调用,协程对象也可以封闭进枚举器,对外界是透明的存在。改进一下代码像这样:


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 ff 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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值