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

Pass the result of a Ruby Thread as a parameter to a method

我有这些控制器方法

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

线程没有 return 值(Thread 对象本身除外)。

这在 javascript 中是一个非常普遍的问题,您在其中使用许多异步函数(例如 setTimeout),这些函数不会像同步函数那样生成 return 值。

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 的方法本身都需要是异步的。

这不适用于同步的控制器操作(除非您使用的是流式响应或服务器推送,但我假设您不是)。

这是一种 'law' 的异步代码,您不能从同步函数调用异步函数,并在不调用 join 的情况下获取 'return value'(并强制它运行 同步)。所以每当你想要一个异步函数的'return value',你需要使调用函数异步,并使用block/callback来读取结果。请注意,这个烦人的过程有时在 javascript 中称为 'callback hell',这就是为什么他们实施 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 显式传递给 callback/block,因此您不必假设调用者可以访问它。

此外,请不要在您的代码中实际放置 sleep 调用,我只使用这些,所以如果您 运行 将代码片段作为脚本,它将起作用。在实际代码中,如果要等到线程完成,则应使用 Thread#join

重申一下,如果您希望及时获得结果以包含在响应中,则不应在控制器操作中使用线程(除非您使用 .join 最后)。