我有这些控制器方法
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
线程没有返回值(Thread对象本身除外)。
这是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的支持)。
但是,假设您从另一个异步方法调用此方法:
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("[email protected]") 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
进行并行处理)。