Потоки не имеют возвращаемых значений (кроме самого объекта Thread).
Это действительно распространенная проблема в javascript, где вы используете много асинхронных функций (таких как setTimeout), которые не выдают returnзначения аналогичны синхронным функциям.
То, что Javascript обычно использует для решения этой проблемы, - это обратные вызовы (а также обещания / 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, и именно поэтому они реализовали обещания / 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("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
вконец).