Ожидание асинхронных вызовов с EventMachine и волокнами Ruby

Я запускаю этот фрагмент кода под 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, описанное в эту статью без использования электронной синхронизации.)

Изменить: вот еще несколько подробностей о том, чего я в конечном итоге пытаюсь достичь. Я разрабатываю Grape API, работающий на Thin, и каждый обработчик маршрута должен последовательно выполнять различные вызовы к хранилищам данных, ZooKeeper, другим службам HTTP и т. д., прежде чем вернуть ответ.

em-synchrony действительно крутая вещь, но я продолжаю сталкиваться с проблемами с выходом из корневого волокна или с результатами, показывающими несинхронные симптомы, как в случае выше. Rack-fiber_pool также кажется потенциально полезным, но я не хочу его использовать, потому что из коробки он ломает все мои модульные тесты Rack::Test.

Я свел свои проблемы к простому примеру, приведенному выше, потому что у меня, похоже, есть фундаментальное непонимание того, как волокна и EventMachine должны использоваться вместе, что мешает мне эффективно использовать более сложные фреймворки.


person breaker    schedule 04.10.2012    source источник
comment
Что касается Rack::Test, вы можете попробовать use сделать это только на своем config.ru и завернуть свои тесты в Fibers, используя блок rspec around или что-то в этом роде.   -  person Renato Zannon    schedule 04.10.2012
comment
Из-за того, что Grape плохо работает с добавленным промежуточным программным обеспечением (всегда помещая его последним, а не первым, где оно должно быть в этом случае), мне пришлось обернуть приложение Grape, используя Rack::Builder в моей конфигурации и добавив Rack::FiberPool к тот. Что полностью обошло проблему модульного тестирования. :)   -  person breaker    schedule 05.10.2012
comment
Вы останавливаете EM сразу после того, как AC было напечатано.   -  person phil pirozhkov    schedule 06.10.2012


Ответы (1)


Вы, наверное, хотели что-то вроде этого:

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

Более концептуальное объяснение:

Файберы помогают распутать асинхронный код, потому что они блокируют и реанимируют фрагменты кода, как и ручные потоки. Это позволяет использовать хитрые трюки в отношении порядка, в котором происходят события. Небольшой пример:

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"), а действия на «параллельных» волокнах не могут (и не должны) рассчитываться. меня не волнует), были ли выполнены действия на других волокнах: мы бы напечатали «BACD», только поменяв местами первые два вызова resume.

Итак, вот как rack-fiber_pool творит чудеса: он помещает каждый запрос, полученный вашим приложением, внутрь волокна (что подразумевает независимость от порядка), а затем ожидает, что вы Fiber.yield выполните действия ввода-вывода, чтобы сервер мог принимать другие запросы. Затем внутри обратных вызовов EventMachine вы передаете блок, содержащий current_fiber.resume, чтобы ваше волокно реанимировалось, когда ответ на запрос/запрос/что-либо готово.

Это уже длинный ответ, но я могу привести пример EventMachine, если он все еще не ясен (я понимаю, что это сложная концепция для грока, я много боролся).


Обновление: я создал пример, который может помочь всем, кто все еще борется с концепциями: https://gist.github.com/renato-zannon/4698724. Рекомендую побегать и поиграть с ним.

person Renato Zannon    schedule 04.10.2012
comment
Спасибо - хотя это не очень помогает, например, в контексте обслуживания чего-либо из веб-приложения, что является моей конечной целью. Я хотел бы выполнить ряд асинхронных действий, подобных этому, при обслуживании HTTP-запроса, а затем вернуть какой-либо результат клиенту. - person breaker; 04.10.2012
comment
Ну, тогда вам, вероятно, следовало упомянуть об этом в своем вопросе :) Можете ли вы отредактировать вопрос, чтобы немного расширить его на то, что именно вы хотите? Но в любом случае основные принципы одинаковы, хотя в среде веб-приложений вам, вероятно, лучше использовать em-synchrony вместе с чем-то вроде rack-fiber_pool вместо развертывания собственной инфраструктуры. - person Renato Zannon; 04.10.2012
comment
Конечно; Я добавил больше деталей. Я боролся с em-synchrony и Rack-Fiber_pool полдня без прогресса, поэтому я решил, что просто упустил какую-то фундаментальную концепцию. - person breaker; 04.10.2012
comment
Спасибо! Я думаю, что теперь я гораздо лучше разбираюсь в этом. Часть о том, что все последовательные действия заключены в одно и то же волокно для установления порядка, определенно помогает. - person breaker; 04.10.2012
comment
Нет, больше ничего не нужно; У меня сегодня все заработало. Спасибо! - person breaker; 05.10.2012