play - как обернуть блокирующий код фьючерсами


Я пытаюсь понять разницу между этими двумя методами с точки зрения функциональности.

class MyService (blockService: BlockService){
   def doSomething1(): Future[Boolean] = {
       //do
       //some non blocking
       //stuff
       val result = blockService.block()
       Future.successful(result)
   }

   def doSomething2(): Future[Boolean] = {
       Future{
          //do
          //some non blocking
          //stuff
          blockService.block()
       }
   }
}

В моем понимании разница между 2 состоит в том, какой поток является фактическим потоком, который будет заблокирован.

Таким образом, если есть поток: thread_1, который выполняет something1, thread_1 будет заблокирован, а если thread_1 выполняется something2, то новый поток будет выполнять его - thread_2, и thread_2 будет заблокирован.

Это правда?

Если так, то там нет ли действительно предпочтительного способа написать этот код? если мне все равно, какой поток в конечном итоге будет заблокирован, то конечный результат будет таким же. dosomething1 кажется странным способ написания этого кода, я бы выбрал dosomething2.

Есть смысл?

4 3

4 ответа:

Да, doSomething1 и doSomething2 блокируют разные потоки, но в зависимости от вашего сценария это важное решение.

Как сказал @AndreasNeumann, у вас могут быть разные контексты выполнения в doSomething2. Представьте себе, что основной контекст выполнения-это тот, который получает HTTP-запросы от ваших пользователей. Блокировать потоки в этом контексте плохо, потому что вы можете легко исчерпать контекст выполнения и повлиять на запросы, которые не имеют ничего общего с doSomething.

Play docs есть лучше объяснение возможных проблем с наличием блокирующего кода:

Если вы планируете написать блокирующий код ввода-вывода или код, который потенциально может выполнять большую нагрузку на процессор, вам нужно точно знать, какой пул потоков несет эту нагрузку, и вам нужно настроить его соответствующим образом. выполнение блокировки ввода-вывода без учета этого, вероятно, приведет к очень низкой производительности от Play framework , например, вы можете видеть, что обрабатывается всего несколько запросов в секунду, в то время как загрузка процессора составляет 5%. Для сравнения, тесты на типичном оборудовании для разработки (например, MacBook Pro) показали, что Play может обрабатывать рабочие нагрузки в сотни или даже тысячи запросов в секунду без пота при правильной настройке.

В вашем случае оба метода выполняются с использованием пула потоков Play default. Я предлагаю вам взглянуть нарекомендуемые рекомендации и посмотреть, нужен ли вам другой контекст выполнения или нет. Я также предлагаю вам: прочитайте Akka docs о диспетчерах и фьючерсах, чтобы лучше понять, что такое выполнение фьючерсов и имеют блокирующий/неблокирующий код.

Этот подход имеет смысл, если вы используете другой execution contexts во втором методе.

Таким образом, имея, например, один для ответа на запросы и другой для блокировки запросов. Таким образом, вы будете использовать обычный playExecutionContext, чтобы приложение работало и отвечало, и отделять blocking операцию в другом.

def doSomething2(): Future[Boolean] = Future{ 
    blocking { blockService.block() }
}( mySpecialExecutionContextForBlockingOperations )

Для получения дополнительной информации: http://docs.scala-lang.org/overviews/core/futures.html#blocking

Вы правы. Я не вижу смысла в досометинге1. Это просто усложняет интерфейс для вызывающего абонента, не предоставляя преимуществ асинхронного API.

Обрабатывает Ли BlockService операцию блокировки?
Обычно, используйте blocking, Как напоминает @Andreas, чтобы сделать операцию блокировки в другой поток подлым.