将Ruby线程的结果作为参数传递给方法

时间:2019-02-12 19:47:02

标签: ruby multithreading ruby-on-rails-5.1

我有这些控制器方法

def student_progress_variables(student_email)
    dashboard = Dashboard.new
    @projects = Thread.new { dashboard.obtain_projects }

    @current_student = obtain_student_with_email(student_email)

    @reviews = obtain_selected_student_project_reviews(@current_student)
    @requests = obtain_student_code_review_requests(@current_student).sort_by do |request|
      request[obtain_code_review_requests_id]
    end

    @review_completions = obtain_student_code_review_completions(@current_student)
    @courses = obtain_projects_courses(@projects.join)
  end

我想将@projects的响应传递给@courses方法调用,但是我一直收到此undefined method 'each' for #<Thread:0x00007f30dc83da10>错误。我已经阅读了文档,但没有任何进展。有什么想法吗?

编辑:我接受@max pleaner的建议。这是代码段的当前状态。我已经看到了更好的结果:

def student_progress_variables(student_email)
    thread1 = Thread.new do 
      @current_student = obtain_student_with_email(student_email)
    end
    thread2 = Thread.new do 
      dashboard = Dashboard.new
      @projects = dashboard.obtain_projects
      @courses = obtain_projects_courses(@projects)
    end
    thread1.join

    thread3 = Thread.new do 
      @reviews = obtain_selected_student_project_reviews(@current_student)
    end

    @requests = obtain_student_code_review_requests(@current_student).sort_by do |request|
      request[obtain_code_review_requests_id]
    end
    @review_completions = obtain_student_code_review_completions(@current_student)
    thread2.join
    thread3.join
  end

1 个答案:

答案 0 :(得分:1)

线程没有返回值(线程对象本身除外)。

这是javascript中一个非常普遍的问题,在该问题中,您使用了许多异步函数(例如setTimeout),它们不会像同步函数那样产生返回值。

JavaScript通常用于处理此问题的是回调(以及promises / async / await)。您可以以块的形式在ruby中使用回调:

  class AsyncClass
    attr_reader :foo, :bar
    def async_method(&callback)
      Thread.new do
        @foo = 1
        @bar = @foo + 1
        callback.call(self)
      end
    end
  end

尽管您可以在任何线程上调用join来暂停代码执行,直到完成为止,但这种方式消除了完全使用线程的目的(除非您正在执行并行处理)。因此,任何调用async_method的方法本身都必须是异步的。

这在同步的控制器动作中将无法工作(除非您正在使用流式响应或服务器推送,但我认为您不会)。

这是一种异步代码的“定律”,您不能从同步函数中调用异步函数,而不能在不调用join的情况下获得“返回值”(并强制其同步运行)。因此,无论何时,只要要异步函数的“返回值”,就需要使调用者函数异步,并使用块/回调读取结果。请注意,此烦人的过程有时在javascript中称为“回调地狱”,这就是为什么它们实现了promises / async / await(顺便说一下,ruby-concurrency gem对此有所支持)的原因。

但是,假设您正在从另一个 async 方法调用此方法:

  class OtherClass
    def initialize
      AsyncClass.new.async_method do |async_class_inst|
        puts async_class_inst.bar
      end
      sleep 1
    end
  end

  OtherClass.new
  # prints 2

在您的情况下,它看起来像这样:

  def student_progress_variables(student_email, &blk)
    dashboard = Dashboard.new
    Thread.new do
      @projects = dashboard.obtain_projects
      @current_student = obtain_student_with_email(student_email)

      @reviews = obtain_selected_student_project_reviews(@current_student)
      @requests = obtain_student_code_review_requests(@current_student).sort_by do |request|
        request[obtain_code_review_requests_id]
      end

      @review_completions = obtain_student_code_review_completions(@current_student)
      @courses = obtain_projects_courses(@projects.join)
      blk.call(self)
    end
  end

但是,同样,您只能从另一个异步方法调用此:

  class AsyncCaller
    def initialize(&callback)
      SomeClass.new.student_progress_variables("student@email.com") do |some_class_inst|
        callback.call(self, some_class_inst)
      end
      sleep 1
    end
  end

  AsyncCaller.new do |async_caller_inst, some_class_inst|
    # .... do things here, thread is done
  end

请注意,在这些示例中,我将self传递给回调,以便将其分配给块变量(例如async_class_inst)。这是因为self的值根据调用该块的位置而变化,如本例所示:

  class A
    def initialize(&blk)
      blk.call
      sleep 1 
    end
  end

  A.new { puts self }
  # prints 'main'

因此,如果您要在线程中对self进行一些操作(就像您一样,通过设置实例变量),则最好将self显式传递给回调/块,因此您不必假设调用者可以访问它。

另外,请不要在您的代码中实际放置sleep调用,我只是使用这些,因此,如果您将摘要作为脚本运行,它将可以正常工作。在实际的代码中,如果要等待线程完成,则应使用Thread#join

重申一下,如果希望及时得到结果以包括在响应中,则不要在控制器操作中使用线程(除非您正在使用.join进行并行处理结束)。