我有这些控制器方法
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
答案 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
进行并行处理结束)。