协程与xpcall
在移植busted测试框架的时候,发现不能在测试用例里面调用协程的yield,核心原因是每个用例是运行在xpcall内,此时调用yield会报错
attempt to yield across metamethod/C-call boundary
示例代码
local func = function()
print("xpcall xxx");
coroutine.yield()--挂起
print("yield");
end
local funcList = {func,func,func}
function run()
local co = coroutine.create(function( ... )
xpcall(function( ... )
for i,func in ipairs(funcList) do
func()
end
end, function(errMsg)
print(errMsg) -- 触发异常 attempt to yield across metamethod/C-call boundary
end)
end)
coroutine.resume(co)
end
run()
想要达到目的
- 能捕获异常
- 能调用协程yield
解决办法
捕获异常
xpcall 通常用来捕获异常,当时因为协程的缘故在这里我们不能直接使用。所以得换个思路,除了xpcall能捕获异常让程序不中断执行外,协程也有类似功能,或者说协程内部函数天然带有 异常捕获机制,所以我们可以利用协程替换掉xpcall
实现机制
local xpcall = function(f,err)
local xco = coroutine.create(f)
local succ,msg = coroutine.resume(xco)
if not succ then
err(msg)
end
end
调用协程方法
如果我们改造了xpcall函数,此时可以调用协程内部的接口了,但是此时有个问题,如果在func内部调用yield此时挂起的是xpcall内部的xco 而外部的协程没有被挂起,还是会往下走。这里遇到了协程嵌套问题。解决办法是不断透传出去。为了好理解,我们在外部协程叫主协程,xpcall内部的协程叫子协程。此时我们在业务内部挂起了子携程,但是主协程没有yield还是会继续运行,所以此时当我们在yiled子协程的时候,根据yield的返回值控制是否挂起主协程。当要恢复子协程的时候由于拿不到xco句柄。我们得先恢复主协程,然后在恢复子协程。这里有点绕。具体代码如下
local xpcall = function(f,err)
local xco = coroutine.create(f)
local succ,msg = coroutine.resume(xco)
while succ and coroutine.status(xco) == "suspended" do --根据子携程状态决定是否透传挂起主协程
local code = coroutine.yield() --再次挂起主协程
if code == "resume_sub" then --根据主协程指令是否恢复子携程
succ,msg = coroutine.resume(xco) --主携程恢复过来的时候再次恢复子携程
end
end
if not succ and msg then
err(msg)
end
end
local func = function()
print(os.clock());
coroutine.yield("await")--挂起子协程 此时走到 local succ,msg = coroutine.resume(xco)
print("resume sub");
end
local funcList = {func,func,func}
function run()
local mainCo = coroutine.create(function( ... )
xpcall(function( ... )
for i,func in ipairs(funcList) do
func()
end
end, function(errMsg)
print(errMsg) -- 触发异常 attempt to yield across metamethod/C-call boundary
end)
end)
coroutine.resume(mainCo)
for i=1,3 do
coroutine.resume(mainCo,"resume_sub") --模拟恢复子携程,其实是先恢复一次主携程,然后恢复子携程,解决协程嵌套问题
end
end
run()