Проблема с очередями в Ruby Eventmachine - PullRequest
2 голосов
/ 14 февраля 2011

У меня есть Http-клиент, написанный на Ruby, который может выполнять синхронные запросы к URL-адресам.Однако для быстрого выполнения нескольких запросов я решил использовать Eventmachine.Идея состоит в том, чтобы поставить в очередь все запросы и выполнить их с помощью eventmachine.

class EventMachineBackend
  ...
  ...
  def execute(request)
    $q ||= EM.Queue.new
    $q.push(request)
    $q.pop {|request| request.invoke}
    EM.run{EM.next_tick {EM.stop}}
  end
  ...
end

Простите за использование глобальной переменной очереди.Я рефакторинг это позже.Является ли то, что я делаю в EventMachineBackend#execute правильным способом использования очередей Eventmachine?

Одна проблема, которую я вижу в своей реализации, заключается в том, что она по существу синхронна.Я нажимаю запрос, высовываю и выполняю запрос и жду его завершения.

Может кто-нибудь предложить лучшую реализацию.

1 Ответ

10 голосов
/ 14 февраля 2011

Ваша логика запроса должна быть асинхронной, чтобы она работала с EventMachine, я предлагаю вам использовать em-http-request . Вы можете найти пример того, как его использовать, здесь , он показывает, как выполнять запросы параллельно. Еще лучшим интерфейсом для параллельного запуска нескольких соединений является класс MultiRequest из одного и того же камня.

Если вы хотите ставить запросы в очередь и выполнять только фиксированное число параллельно, вы можете сделать что-то вроде этого:

EM.run do
  urls = [...] # regular array with URLs
  active_requests = 0

  # this routine will be used as callback and will
  # be run when each request finishes
  when_done = proc do
    active_requests -= 1
    if urls.empty? && active_requests == 0
      # if there are no more urls, and there are no active
      # requests it means we're done, so shut down the reactor
      EM.stop
    elsif !urls.empty?
      # if there are more urls launch a new request
      launch_next.call
    end
  end

  # this routine launches a request
  launch_next = proc do
    # get the next url to fetch
    url = urls.pop
    # launch the request, and register the callback
    request = EM::HttpRequest.new(url).get
    request.callback(&when_done)
    request.errback(&when_done)
    # increment the number of active requests, this
    # is important since it will tell us when all requests
    # are done
    active_requests += 1
  end

  # launch three requests in parallel, each will launch
  # a new requests when done, so there will always be 
  # three requests active at any one time, unless there
  # are no more urls to fetch
  3.times do
    launch_next.call
  end
end

Будьте бдительны, возможно, есть некоторые детали, которые я пропустил в коде выше.

Если вы считаете, что в моем примере трудно следовать логике, добро пожаловать в мир четного программирования. Это действительно сложно написать читаемый код. Все идет в обратном направлении. Иногда это помогает начать чтение с конца.

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

launch_next = proc do
  urls.pop do |url|
    request = EM::HttpRequest.new(url).get
    request.callback(&launch_next)
    request.errback(&launch_next)
  end
end

Кроме того, имейте в виду, что мой код на самом деле ничего не делает с ответом. Ответ будет передан в качестве аргумента подпрограмме when_done (в первом примере). Я также делаю то же самое для успеха и ошибок, чего вы, возможно, не захотите делать в реальном приложении.

...