我在Ruby 1.9.2下运行此代码片段:
require "eventmachine"
require "fiber"
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
EM.add_timer(2) do
print "B"
current_fiber.resume("D")
end
Fiber.yield
end
print "A"
val = fiber.resume
print "C"
print val
EM.stop
end
我希望输出为" ABCD",程序在" A"之后暂停两秒钟。然而,它只是打印出" AC"马上,然后在退出前等待两秒钟。我做错了什么?
(作为参考,我试图在不使用em-synchrony的情况下重现this article中描述的em-synchrony风格的行为。)
编辑:这里有一些关于我最终要完成什么的更多细节。我正在开发一个在Thin上运行的Grape API,并且每个路由处理程序必须在返回响应之前对数据存储,ZooKeeper,其他HTTP服务等进行各种串行调用。
em-synchrony非常酷,但是我一直遇到从根光纤屈服或结果显示上述情况的非同步症状的问题。 rack-fiber_pool似乎也很有用,但我不愿意使用它,因为开箱即用它会破坏我所有的Rack :: Test单元测试。我将问题简化为上面的简单示例,因为我似乎对如何一起使用光纤和EventMachine产生了根本的误解,导致我无法有效地使用更复杂的框架。
答案 0 :(得分:9)
你可能想要这样的东西:
require "eventmachine"
require "fiber"
def value
current_fiber = Fiber.current
EM.add_timer(2) do
puts "B"
current_fiber.resume("D") # Wakes the fiber
end
Fiber.yield # Suspends the Fiber, and returns "D" after #resume is called
end
EM.run do
Fiber.new {
puts "A"
val = value
puts "C"
puts val
EM.stop
}.resume
puts "(Async stuff happening)"
end
这应该产生以下结果:
A
(Async stuff happening)
B
C
D
一个更概念性的解释:
Fibers帮助解开异步代码,因为它们的代码块被暂停和恢复,就像手动线程一样。这允许有关事情发生顺序的巧妙技巧。一个小例子:
fiberA = Fiber.new {
puts "A"
Fiber.yield
puts "C"
}
fiberB = Fiber.new {
puts "B"
Fiber.yield
puts "D"
}
fiberA.resume # prints "A"
fiberB.resume # prints "B"
fiberA.resume # prints "C"
fiberB.resume # prints "D"
因此,当在光纤上调用#resume
时,它会从块的开头(对于新光纤)或之前的Fiber.yield
调用恢复执行,然后再执行执行直到找到另一个Fiber.yield
或块结束。
重要的是要注意,在光纤内部放置一系列动作是表示它们之间的时间依赖性的一种方式(puts "C"
不能在puts "A"
之前运行),而对“并行”的动作“光纤不能指望(并且不应该关心)其他光纤上的动作是否已经执行:我们只能通过交换前两个resume
来打印”BACD“。
所以,这里是rack-fiber_pool
的神奇之处:它将你的应用程序接收到的每个请求放在一个光纤中(这意味着顺序无关),然后期望你对IO动作Fiber.yield
,以便服务器可以接受其他请求。然后,在EventMachine回调中,您传入一个包含current_fiber.resume
的块,以便在查询/请求/其他任何内容准备就绪时,您的光纤将被重新生成。
这已经是一个冗长的答案了,但我可以提供一个EventMachine示例,如果它仍然不清楚(我认为这是一个毛茸茸的概念,我很努力)。
更新:我创建了一个示例,可以帮助仍在努力解决这些概念的任何人:https://gist.github.com/renato-zannon/4698724。我建议跑步和玩它。