Передать результат Ruby Thread в качестве параметра методу - PullRequest
0 голосов
/ 12 февраля 2019

У меня есть эти методы контроллера

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 голосов
/ 13 февраля 2019

Потоки не имеют возвращаемых значений (кроме самого объекта 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 вконец).

...