Ruby中的任务/未来

时间:2012-02-21 03:43:42

标签: ruby asynchronous

模式的惯用Ruby模拟是什么,表示可能延迟的异步计算,可以订阅它的完成?即.NET System.Threading.Task或Python 3.x concurrent.futures.future

请注意,这并不一定意味着多线程 - “未来”对象的实际实现可能会使用其他方式来调度工作和获取结果,并且超出了问题的范围。该问题严格关注呈现给对象用户的API。

6 个答案:

答案 0 :(得分:9)

我不确定vanilla Ruby,但EventMachine有deferrables

另外,请查看this article

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}" }
}

答案 1 :(得分:1)

光纤?

Fibers是在Ruby中实现轻量级协同并发的原语。基本上它们是创建可以暂停和恢复的代码块的一种方法,就像线程一样。主要区别在于它们永远不会被抢占,并且调度必须由程序员而不是VM完成。 link

答案 2 :(得分:1)

您可以使用resque之类的作业队列 编写了一些纯红宝石的快速示例

  1. 通过分支子进程

    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
    
  2. 通过在线程上进行回调

    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
    

  3. 线程比分叉子进程消耗更少的内存,但分叉是健壮的。线程中未处理的错误可能会导致整个系统崩溃。虽然子进程中出现未处理的错误,但只会降低子进程

    其他人指出纤维和事件机器(使用EM :: Deferrable和EM.defer)是另一种选择

    光纤和线程需要仔细编码。代码可能是微妙的错误。
    光纤也使用先发制人的多任务处理,因此代码库必须表现良好

    Eventmachine很快但它是一个独特的世界(就像在python中扭曲一样)。它有自己独立的IO堆栈,因此必须编写所有库以支持eventmachine。话虽如此,我认为图书馆支持不是eventmachine的问题

答案 3 :(得分:1)

也许我错过了一些东西,但如果情况与你在对deepak的回答中所描述的那样,那么为什么不将C API包装为Ruby扩展并提供一个Ruby方法来接受与你需要的回调相对应的块?这也是非常惯用的Ruby。

下面是一个示例章节,介绍如何使用为Ruby 1.9更新的“Pickaxe”手册扩展Ruby with C:http://media.pragprog.com/titles/ruby3/ext_ruby.pdf

<强>更新 以下是处理Ruby及其C接口中的Ruby异常的一些链接。

答案 4 :(得分:1)

lazy.rb提供“期货”,但它们似乎与您描述的完全不同(或者我预期):

  

此外,该库提供期货,其中计算立即在后台线程中运行。

因此,您无法在以后计算它们,或者通过其他方式将值插入它们(可能来自网络)。

答案 5 :(得分:0)

thread宝石可能会引起人们的兴趣。您可以创建一个在后台处理内容的线程池。宝石还支持许多其他功能,如未来,延迟等。看看the github repo

它似乎适用于各种红宝石版本,而不仅仅是1.9+,这就是我使用它的原因。