对于这个问题的长度,我事先表示歉意。我试图使其尽可能简洁,但这只是一个相当复杂的野兽。
在Ierusalimschy的 Lua编程(第4版)的第24章中,作者展示了任何异步I / O库的玩具实现(“丑陋”),例如这样的 1 :
-- filename: async.lua
-- Based (with several modifications) on Listing 24.3 (p. 246) of *Programming
-- in Lua*, 4th edition.
local async = {}
local queue = {}
local function enqueue (command) table.insert(queue, command) end
function async.readline (stream, callback)
enqueue(function () callback(stream:read()) end)
end
function async.writeline (stream, line, callback)
enqueue(function () callback(stream:write(line)) end)
end
function async.stop () enqueue("stop") end
function async.runloop ()
while true do
local next_command = table.remove(queue, 1)
if next_command == "stop" then break end
next_command()
end
end
return async
作者使用这个玩具库来说明协程的一些应用,例如下面显示的用于在异步库之上运行“同步代码”的方案 2 :
-- Based (with several modifications) on Listing 24.5 (p. 248) of *Programming
-- in Lua*, 4th edition.
local async = require "async"
function run (synchronous_code)
local co = coroutine.create(function ()
synchronous_code()
async.stop()
end)
local wrapper = function ()
local status, result = assert(coroutine.resume(co))
return result
end
wrapper()
async.runloop()
end
function getline (stream)
local co = coroutine.running()
local callback = function (line) assert(coroutine.resume(co, line)) end
async.readline(stream, callback)
local line = coroutine.yield()
return line
end
function putline (stream, line)
local co = coroutine.running()
local callback = function () assert(coroutine.resume(co)) end
async.writeline(stream, line, callback)
coroutine.yield()
end
作者使用此技术实现了一个功能,该功能以从stdin读取的行以相反的顺序打印到stdout:
function synchronous_code ()
local lines = {}
local input = io.input()
local output = io.output()
while true do
local line = getline(input)
if not line then break end
table.insert(lines, line)
end
for i = #lines, 1, -1 do putline(output, lines[i] .. "\n") end
end
run(synchronous_code)
一般的想法是run
函数创建一个协程,该协程将自己(通过getline
和putline
创建的回调)“注册”到异步库的主循环中。每当异步库的主循环执行这些回调之一时,它就会恢复协程,协程可以做更多的工作,包括在主循环中注册下一个回调。
run
函数通过调用wrapper
函数使球滚动,而该函数又“恢复”(实际上是启动)协程。协程然后运行,直到遇到第一个yield语句为止,在本示例中,该语句在getline
内getline
已在async
库的队列中注册了回调之后发生。然后wrapper
函数重新获得控制权并返回。最后,run
调用async.runloop
。 async.runloop
开始处理其队列时,将恢复协程,然后我们离开。 “同步代码”(在协程中运行)一直持续到下一个getline
或putline
产生(注册回调后),并且async
的主循环再次接管为止。>
到目前为止,一切都很好。但随后,在练习24.4(p。249)中,作者问:
练习24.4::为基于协程的库编写行迭代器(清单24.5),以便您可以使用 for 循环读取文件。
(“清单24.5”是指上面第二个代码段中的代码,其中定义了run
,getline
和putline
。)
我完全被这个困扰。在上面的示例中,协程通过将它们读取的行写到stdout来“交付”它可以单独完成的所有工作。相比之下,练习24.4要求的迭代器必须将其行传递给进行迭代的不同协程。
我能想象到的唯一方法是,两个协程可以互相恢复。那有可能吗?我无法构造一个简单的示例,希望看到能做到这一点的代码 3 。
另外,在我看来,要使其完全起作用,就需要使用一种write
方法(以便可以将其传递给putline
)来实现最终负责的对象。用于将行(以某种方式)传递到迭代器的协程。
1 我已经更改了一些肤浅的细节,例如变量的名称,缩进等。整体结构和功能未更改。
2 再次,我更改了一些无关紧要的细节,以使代码更容易理解。
3 值得注意的是,本章剩下的两个练习(24.5和24.6)都是关于实现涉及多个并发协程的系统的。因此,不难想象练习24.4也是关于两个协程互相交谈的。
答案 0 :(得分:1)
我相信您完全没有考虑这一练习。以我的理解,您只打算为迭代器编写一种同步样式,该迭代器在提供给run
函数的同步代码中运行。以第三个代码块为基础:
function for_file(file)
return function(file)
return getline(file)
end, file, nil
end
function synchronous_code ()
local lines = {}
local input = io.input()
local output = io.output()
for line in for_line(input) do
table.insert(lines, line)
end
for i = #lines, 1, -1 do putline(output, lines[i] .. "\n") end
end
run(synchronous_code)
正如您所看到的那样,您真的不需要完全了解协程,这样就可以了,这就是库的重点。