Ruby中的任务/未来

Ruby中的任务/未来,ruby,asynchronous,Ruby,Asynchronous,一个模式的惯用Ruby模拟是什么,它表示一个可能延迟的异步计算,并可能订阅它的完成?i、 e.类似.NETSystem.Threading.Task或Python 3.xconcurrent.futures.future的东西 请注意,这并不一定意味着多线程,“未来”对象的实际实现同样可能使用其他方式来安排工作并获得结果,这超出了问题的范围。这个问题与提供给对象用户的API密切相关。Fiber 光纤是在Ruby中实现轻量级协作并发的原语。基本上,它们是一种创建可以暂停和恢复的代码块的方法,就像线

一个模式的惯用Ruby模拟是什么,它表示一个可能延迟的异步计算,并可能订阅它的完成?i、 e.类似.NET
System.Threading.Task
或Python 3.x
concurrent.futures.future
的东西

请注意,这并不一定意味着多线程,“未来”对象的实际实现同样可能使用其他方式来安排工作并获得结果,这超出了问题的范围。这个问题与提供给对象用户的API密切相关。

Fiber


光纤是在Ruby中实现轻量级协作并发的原语。基本上,它们是一种创建可以暂停和恢复的代码块的方法,就像线程一样。主要的区别是它们从不被抢占,调度必须由程序员而不是虚拟机来完成

我不确定香草红宝石,但EventMachine有

还有,看看


您可以使用作业队列,如
为纯ruby编写了一些快速示例

  • 通过分叉子进程

    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
    

  • 线程比分支子进程消耗更少的内存,但分支是健壮的。线程中未经处理的错误会导致整个系统崩溃。而子进程中未处理的错误只会导致子进程停止

    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
    
    其他人指出,fibers和eventmachine(使用EM::Deferrable和EM.defer)是另一种选择

    纤维和线需要仔细编码。代码可能以微妙的方式出错。
    此外,光纤使用先发制人的多任务处理,因此代码库必须表现良好


    Eventmachine速度很快,但它是一个独占的世界(就像python中的twisted)。它有自己独立的IO堆栈,因此必须编写所有库来支持eventmachine。话虽如此,我不认为对eventmachine的库支持是个问题,也许我遗漏了什么,但是如果情况如您在对deepak的响应中所描述的那样,那么为什么不将C API包装为Ruby扩展,并提供一个Ruby方法来接受与所需回调对应的块呢?这也是非常地道的Ruby

    下面是一个示例章节,介绍如何使用C扩展Ruby,这是为Ruby 1.9更新的“Pickaxe”一书中的内容

    更新: 下面是一些在Ruby及其C接口中处理Ruby异常的链接

      • 提供了“未来”,但它们似乎与您描述的不完全相同(或者我预期的):

        此外,该库还提供了未来,即在后台线程中立即运行计算


        因此,您以后无法计算它们,也无法通过其他方式(可能是从网络)将值插入其中。

        我发现这非常有用:

        它是一个只允许将方法推送到后台任务上的gem。

        这个gem可能会引起人们的兴趣。您可以创建一个线程池,在后台处理内容。gem还支持很多其他功能,比如future、delay等。看看吧


        它似乎适用于范围广泛的ruby版本,而不仅仅是1.9+,这就是我使用它的原因

        这与并发本身无关,更多的是异步。假设我已经有一个不是用Ruby编写的库,但它返回futures(例如,它是一个.NET库,返回System.Threading.Task)。我需要知道如何在Ruby中以Ruby开发人员最自然的方式最好地公开它。实际上,Fiber与并发无关,它是非阻塞模型,即异步,就像node.js一样。你可以在ruby框架中检查这项技术的使用情况,我明白这一点。不过,就我所见,光纤是一种要么全有要么全无的东西——也就是说,如果你不控制所涉及的一些API的实现,而它们不使用光纤,你自己就不能真正使用它们,因为你根本没有光纤可供使用。我需要更高层次的东西,在任何给定的情况下抽象出异步的实现细节,并为任何类型的异步操作提供一个通用的通用接口。如果core Ruby中缺少标准模式,我确实需要看看各种框架提供了什么,看看是否可以在那里派生出一个通用模式。这是一个很好的指针-你知道其他框架有类似的东西吗?@PavelMinaev:从我的想法来看-没有。但我会想:)我不是在实现异步,我是在向Ruby公开一些已经实现的东西。“这个问题与呈现给对象用户的API密切相关。”@PavelMinaev,如果我的评论正确的话。你想要一个非侵入性的解决方案吗?这两种解决方案都有点侵入性。对于我给出的示例,必须在代码周围使用包装器。我总是能从itImagine中获得灵感,我有一个用C编写的DLL,它公开了一个异步函数。异步是以回调的形式出现的,即您调用它,并提供在操作完成后调用的回调。具体如何实现是一个实现细节——DLL可以使用线程、光纤或其他东西。我需要以Ruby惯用的方式将上述函数包装并公开给Ruby代码,但我无法触及它的实现。听起来像是backgroundrb可能会有所帮助,请检查:这是否涉及web堆栈(webserver?db?cache?)35; hopingfor+100rep@edwardsharp不,这不涉及web堆栈。事实上,它可以,但它更通用-这是关于异步操作的API,特别是回调,不管异步是如何实际实现的。伟大的阅读,实现了一个幼稚的未来API,可能会有所帮助。我最近写了一个承诺库:。它被设计为与异步i/o一起使用