延迟调用

lua-users home
wiki

使用 C 闭包进行延迟函数调用。

描述

有时需要在程序中的某个点定义函数调用,但延迟其执行到以后的时间。例如,事件驱动系统需要序列化对异步发生的事件的响应。我们需要在事件发生时将响应函数入队,然后在对早期(或更高优先级)事件的响应完成后出队并执行它们。如果响应函数是无参数的,这很简单 - 只需将函数存储在一个表中即可。但是,如果响应函数需要参数来进一步描述事件,就会出现问题 - 参数在函数入队时已知,但在执行时未知。

这是使用闭包的典型案例,如果整个系统是用 Lua 编写的,这很简单。但是,我们可能希望用 C 编写排队系统,而只用 Lua 编写事件响应函数。使用 C API 无法直接封闭 Lua 函数,就像在 Lua 中使用词法作用域一样。

这里介绍的方案使用 C 闭包来封装任意 Lua 函数及其调用参数。当 C 闭包被调用时,它从其上值中恢复 Lua 函数及其参数,进行调用并返回结果。由于 C 闭包只是一个 Lua 函数,因此它可以存储在注册表或任何其他表中。

此方案也可以在 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 代码

以下是 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*,最后一个参数位于顶部。

JohnHind

使用 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。


最近更改 · 偏好设置
编辑 · 历史
最后编辑于 2015 年 3 月 15 日上午 8:05 GMT (差异)