延迟调用 |
|
有时需要在程序中的某个点定义函数调用,但延迟其执行到以后的时间。例如,事件驱动系统需要序列化对异步发生的事件的响应。我们需要在事件发生时将响应函数入队,然后在对早期(或更高优先级)事件的响应完成后出队并执行它们。如果响应函数是无参数的,这很简单 - 只需将函数存储在一个表中即可。但是,如果响应函数需要参数来进一步描述事件,就会出现问题 - 参数在函数入队时已知,但在执行时未知。
这是使用闭包的典型案例,如果整个系统是用 Lua 编写的,这很简单。但是,我们可能希望用 C 编写排队系统,而只用 Lua 编写事件响应函数。使用 C API 无法直接封闭 Lua 函数,就像在 Lua 中使用词法作用域一样。
这里介绍的方案使用 C 闭包来封装任意 Lua 函数及其调用参数。当 C 闭包被调用时,它从其上值中恢复 Lua 函数及其参数,进行调用并返回结果。由于 C 闭包只是一个 Lua 函数,因此它可以存储在注册表或任何其他表中。
此方案也可以在 Lua 中体现,以简化使用闭包创建延迟函数调用的过程,如果我们首先从 Lua 角度来理解它,则更容易理解。
稍后介绍的 C 代码发布了一个新的全局函数createdeferredcall,使用方法如下
local func = function(p1, p2, p3) print("func", p1, p2, p3); return "ret1","ret2"; end local dfunc = createdeferredcall(func, "call1", "call2", "call3")
dfunc只是一个保存 Lua 函数的变量,但此函数现在封装了原始函数func及其三个字符串参数的值(可以有任意数量的任意 Lua 类型参数)。我们可以像对待任何 Lua 变量一样对待它。稍后,当我们想要执行函数时,我们只需调用dfunc,无需参数
print(dfunc())
这将给出以下结果
>func call1 call2 call3 >ret1 ret2
如果调用过程中出现错误,系统会报告错误并终止,类似于正常的调用操作。但是,我们可以将错误函数传递给调用,它将像 *pcall* 一样运行。
print(dfunc(function() print("inerrorfunc"); return "errormess"; end))
这将产生以下结果
>func call1 call2 call3 >true ret1 ret2
如果在 *func* 中引入错误,调用将返回布尔值 *false*,后跟错误函数提供的消息。
>inerrorfunc >false errormess
这在概念上和实践上都比使用词法作用域在标准 Lua 中生成闭包更直接。然而,真正的优势在于 *createdeferredcall* 也可以作为新的 C API 函数使用,这使我们能够做以前无法做到的事情 - 在 C 中从 Lua 定义的函数创建闭包。
以下是 C 代码
static int Lua_DcallDelegate(lua_State* L) { int efun = (lua_isfunction(L, 1))? 1 : luaL_optint(L, 1, 0); int nret = luaL_optint(L, 2, LUA_MULTRET); lua_checkstack(L, 1); lua_pushboolean(L, TRUE); int sm = lua_gettop(L); int ix = 1; while (!lua_isnone(L, lua_upvalueindex(ix))) { lua_checkstack(L, 1); lua_pushvalue(L, lua_upvalueindex(ix++)); } ix--; if ((ix < 1) || (!lua_isfunction(L, (-1 * ix)))) return luaL_error(L, "Bad Deferred Call"); if (lua_pcall(L, ix - 1, nret, efun) == 0) return (efun == 0)? lua_gettop(L) - sm : lua_gettop(L) - sm + 1; else { lua_pushboolean(L, FALSE); lua_replace(L, sm); return (efun == 0)? lua_error(L) : 2; } } static void luaX_pushdcall(lua_State* L, int nargs) { luaL_checktype(L, 1, LUA_TFUNCTION); lua_pushcclosure(L, Lua_DcallDelegate, nargs + 1); } static int luaX_dcall(lua_State* L, int nresults, int errfunc) { lua_checkstack(L, 2); lua_pushinteger(L, errfunc); lua_pushinteger(L, nresults); return lua_pcall(L, 2, nresults, errfunc); } static int LuaMakeDeferredCall(lua_State* L) { luaX_pushdcall(L, lua_gettop(L) - 1); return 1; } lua_pushcfunction(L, LuaMakeDeferredCall); lua_setglobal(L, "makedeferredcall");
*luaX_pushdcall* 和 *luaX_dcall* 是两个新的 API 函数,它们紧密遵循 *lua_call* 使用的约定。*luaX_pushdcall* 需要堆栈上的 Lua 函数,在 *nargs* 参数下方,最后一个参数位于堆栈顶部。退出时,参数已被弹出,函数被替换为函数 *Lua_DcallDelegate* 上的 C 闭包。*luaX_dcall* 需要 C 闭包位于堆栈顶部。对于 *lua_call*,*nresults* 指定要获取的返回参数数量(它可以是键 LUA_MULTRET)。参数 *errfunc* 为 0 表示没有错误函数,或者堆栈上错误函数的位置,类似于 *lua_pcall*。
如果 *errfunc* 为 0,则封装函数中的任何错误都是终止性的,否则返回参数将位于堆栈上,最后一个参数位于顶部。
如果识别出 *errfunc*,则封装函数中的任何错误都会导致 *luaX_dcall* 在 *errfunc* 返回的字符串消息下返回布尔值 *false*,否则它将在返回参数下返回布尔值 *true*,最后一个参数位于顶部。
使用 LuaK,它是一个完整的 Lua 5.x 发行版,具有延迟语义支持。
例如
function foo() defer defer print("defer in defer") end print("defer") end print("foo") end foo() => output foo defer defer in defer
源代码:[email protected]:peterk9999/LuaK.git
如果你想看到它与 lua 5.1.1 之间的区别,请查看此 [email protected]:rst256/LuaK__diff.git 这不是 fork 也不分支,这个仓库只是为了查看区别。代码实际上在 2015 年 3 月 15 日 13:17:43。