2014-02-11 3 views
1

Я хотел бы запустить серию Proc s, в указанном порядке (то есть они не могут выполняться асинхронно). Некоторым из них может потребоваться сколько угодно времени.Как я могу запускать синхронные длительные операции в EventMachine без блокировки реактора?

Мой код работает в контексте реактора EventMachine. Существует ли известная идиома для написания такого кода без, блокирующего главный реактор?

ответ

3

Как сказал @maniacalrobot, используя EM.defer/deferrable, можно пропустить прок, не блокируя реактор. Но тогда вы вводите «callback hell», когда вам нужно последовательно запускать несколько procs.

Я знаю два решения, чтобы сделать код более читаемым: обещания и волокна.

Обещание дает хороший API сочинить асинхронные вызовы, есть много хороших статьи там, в том числе:

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

Вот вспомогательный метод для выполнения процедурного асинхронно (отложенный), но по-прежнему блокировать код вызова без блокировки основного реактора (это магия Fibers):

def deferring(action) 
    f = Fiber.current 

    safe_action = proc do 
    begin 
     res = action.call 
     [nil, res] 
    rescue => e 
     [e, nil] 
    end 
    end 

    EM::defer(safe_action, proc { |error, result| f.resume([error, result]) }) 

    error, result = Fiber.yield 

    raise error if error 

    result 
end 

Пример использования:

action1_res = deferring(proc do 
    puts 'Async action 1' 
    42 
end 

begin 
    deferring(proc do 
    puts "Action1 answered #{action1_res}" 
    raise 'action2 failed' 
    end) 
rescue => error 
    puts error 
end 
+0

Итак, для более простого примера я могу просто назвать 'deferring (proc1); отложив (proc2); deffering (proc3) '?? – Ovesh

+0

Кроме того, технически, отложенная процедура может завершиться до вызова 'Fiber.yield'. что происходит, когда вы вызываете 'resume' на волокно, которое не заблокировано? (Я понимаю, что это может быть крайний вопрос noob) – Ovesh

+0

@ Овеш, да к первому вопросу, это простейший случай, без возвращаемого значения или обработки ошибок. – patatepartie

0

Любой код, который обычно блокирует главный контур реактора, должен быть запущен с использованием EM#defer. EM#defer принимает два блока в качестве аргументов, первый блок запускается в другом потоке и не должен блокировать реактор. Можно передать второй, необязательный блок, который будет вызываться, когда первый завершит (он также получит результат первого блока).

Дальнейшее чтение https://github.com/eventmachine/eventmachine/wiki/EM::Deferrable-and-EM.defer

приведен пример цепочки 2 длинных запущенных операций будет выглядеть следующим образом:

logic_block = Proc.new { long_running_operation } 
callback = Proc.new { |long_running_operation_result| EM.defer next_long_running_operation } 

EM.defer logic_block, callback 

Осторожно, второй (обратный вызов) блок запускается на контуре реактора, так что если вы» планируйте объединение нескольких блоков кода с длинным ходом вместе, вам нужно будет снова вызвать EM.defer внутри обратных вызовов.

+0

Это хорошее решение для двух операций, но не серия процессов. Я предполагаю, что это просто вопрос о цепочке proc после proc. Разве нет хорошей идиомы для этого? – Ovesh

+1

Да, я думал, что этот точный пример не очень хорошо масштабируется. Лучшая идея, которую я мог придумать, заключалась в том, чтобы хранить ваши procs в массиве, а затем иметь простую операцию отсрочки, которая перенесет первый объект proc из массива, запустит его, а затем вызовет в завершении. Это означало бы, что у вас может быть любое количество цепочечных операций без необходимости использования какого-либо дополнительного шаблона кода для управления цепочкой. – maniacalrobot

+0

Мне нравится эта идея.Я немного удивлен, что для этого нет никакой идиомы или api, но я возьму ваше слово за это. – Ovesh