Задача/будущее в Ruby
Что такое идиоматический аналог Ruby шаблона, который представляет потенциально отложенное асинхронное вычисление с возможностью подписаться на его завершение? то есть что-то вроде строк .NET System.Threading.Task
или Python 3.x concurrent.futures.future
.
Обратите внимание, что это не обязательно подразумевает многопоточность - фактическая реализация "будущего" объекта так же вероятно использует другой способ планирования работы и получения результата и выходит за рамки вопроса. Вопрос касается строго API, который предоставляется пользователю объекта.
Ответы
Ответ 1
Я не уверен в ванильном Ruby, но EventMachine отменил.
Кроме того, ознакомьтесь с этой статьей.
EM.run {
detector = LanguageDetector.new("Sgwn i os yw google yn deall Cymraeg?")
detector.callback { |lang| puts "The language was #{lang}" }
detector.errback { |error| puts "Error: #{error}" }
}
Ответ 2
Fiber?
Волокна являются примитивами для реализации облегченного кооператива concurrency в Ruby. В основном это средство создания кодовых блоков, которые могут быть приостановлены и возобновлены, подобно потокам. Основное различие заключается в том, что они никогда не выгружаются и что планирование должно выполняться программистом, а не виртуальной машиной. ссылка
Ответ 3
Вы можете использовать очередь заданий, например resque
Закодировали некоторые быстрые примеры для чистого рубина
-
путем разбиения дочернего процесса
rd, wr = IO.pipe
p1 = fork do
rd.close
# sleep is for demonstration purpose only
sleep 10
# the forked child process also has a copy of the open file
# handles, so we close the handles in both the parent and child
# process
wr.write "1"
wr.close
end
wr.close
puts "Process detaching | #{Time.now}"
Process.detach(p1)
puts "Woot! did not block | #{Time.now}"
1.upto(10) do
begin
result = rd.read_nonblock(1)
rescue EOFError
break
rescue Exception
# noop
end
puts "result: #{result.inspect}"
system("ps -ho pid,state -p #{p1}")
sleep 2
end
rd.close
__END__
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.6.0]
Process detaching | 2012-02-28 17:05:49 +0530
Woot! did not block | 2012-02-28 17:05:49 +0530
result: nil
PID STAT
5231 S+
result: nil
PID STAT
5231 S+
result: nil
PID STAT
5231 S+
result: nil
PID STAT
5231 S+
result: nil
PID STAT
5231 S+
result: "1"
PID STAT
-
путем обратного вызова потока
require 'thread'
Thread.abort_on_exception = true
module Deferrable
def defer(&block)
# returns a thread
Thread.new do
# sleep is for demonstration purpose only
sleep 10
val = block.call
# this is one way to do it. but it pollutes the thread local hash
# and you will have to poll the thread local value
# can get this value by asking the thread instance
Thread.current[:human_year] = val
# notice that the block itself updates its state after completion
end
end
end
class Dog
include Deferrable
attr_accessor :age, :human_age
attr_accessor :runner
def initialize(age=nil)
@age = age
end
def calculate_human_age_as_deferred!
self.runner = defer do
# can do stuff with the values here
human_age = dog_age_to_human_age
# and finally publish the final value
after_defer { self.human_age = human_age }
# return value of the block. used in setting the thread local
human_age
end
end
protected
def dog_age_to_human_age
(self.age / 7.0).round(2)
end
def after_defer(&block)
block.call
end
end
dog = Dog.new(8)
dog.calculate_human_age_as_deferred!
1.upto(10) do
sleep 2
puts "status: #{dog.runner.status} | human_age: #{dog.human_age.inspect}"
break unless dog.runner.status
end
puts "== using thread local"
dog = Dog.new(8)
dog.calculate_human_age_as_deferred!
1.upto(10) do
sleep 2
puts "status: #{dog.runner.status} | human_age: #{dog.runner[:human_year].inspect}"
break unless dog.runner.status
end
__END__
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.6.0]
status: sleep | human_age: nil
status: sleep | human_age: nil
status: sleep | human_age: nil
status: sleep | human_age: nil
status: false | human_age: 1.14
== using thread local
status: sleep | human_age: nil
status: sleep | human_age: nil
status: sleep | human_age: nil
status: sleep | human_age: nil
status: false | human_age: 1.14
потоки потребляют меньше памяти, чем разветвление дочернего процесса, но разветвление является надежным. Необработанная ошибка в потоке может снизить всю систему. в то время как необработанная ошибка в дочернем процессе, приведет к уничтожению дочернего процесса
Другие люди указали, что волокна и eventmachine (с использованием EM:: Deferrable и EM.defer) являются еще одним вариантом
Волокна и нити нуждаются в тщательном кодировании. код может быть неправильным тонким способом.
Кроме того, волокна используют упреждающую многозадачность, поэтому кодовая база должна хорошо вести себя
Eventmachine работает быстро, но это эксклюзивный мир (например, скрученный в python). У этого есть свой отдельный стек IO, поэтому все библиотеки должны быть написаны для поддержки eventmachine. Сказав это, я не думаю, что поддержка библиотеки является проблемой для eventmachine
Ответ 4
Может быть, я что-то упустил, но если ситуация в вашем ответе на deepak описана вами, то почему бы не обернуть C API как расширение Ruby и предоставить метод Ruby, который принимает блок, соответствующий вашему обратному вызову? Это также было бы очень идиоматичным Ruby.
Здесь приведен пример главы, посвященной расширению Ruby с C из книги "Pickaxe", обновленной для Ruby 1.9: http://media.pragprog.com/titles/ruby3/ext_ruby.pdf.
Update:
Вот некоторые ссылки, связанные с исключениями Ruby в Ruby и в интерфейсе C.
Ответ 5
lazy.rb предоставляет "фьючерсы", но они не кажутся точно такими же, как вы описываете (или я бы ожидал)
Кроме того, библиотека предоставляет фьючерсы, где вычисление выполняется сразу в фоновом потоке.
Итак, вы не можете вычислить их позже или вставить в них значения (возможно, из сети) другими способами.
Ответ 6
Я нашел, что это очень полезно:
https://github.com/wireframe/backgrounded
Это драгоценный камень, который просто позволяет нажимать методы на фоновую задачу.
Ответ 7
Может быть интересен thread драгоценный камень. Вы можете создать пул потоков, который обрабатывает материал в фоновом режиме. Драгоценный камень также поддерживает множество других функций, таких как будущее, задержка и т.д. Посмотрите репозиторий github.
Кажется, что он работает с широким диапазоном рубиновых версий, а не только с 1.9+, поэтому я и использую это.