Scala - фьючерсы и concurrency
Я пытаюсь понять фьючерсы Scala, исходящие из фона Java: я понимаю, что вы можете написать:
val f = Future { ... }
то у меня есть два вопроса:
- Как планируется это будущее? Автоматически?
- Какой планировщик будет использовать? В Java вы должны использовать исполнителя, который может быть пулом потоков и т.д.
Кроме того, как я могу достичь scheduledFuture
, который выполняется после определенной временной задержки? Благодаря
Ответы
Ответ 1
Блок Future { ... }
- это синтаксический сахар для вызова Future.apply
(как я уверен, вы знаете Maciej), передавая в блок кода как первый аргумент.
Глядя на docs для этого метода, вы можете видеть, что он принимает неявный ExecutionContext
- и именно этот контекст определяет, как он будет выполнен. Таким образом, чтобы ответить на ваш второй вопрос, будущее будет выполняться тем, что ExecutionContext находится в неявной области (и, конечно, если это неоднозначно, это ошибка времени компиляции).
Во многих случаях это будет один из import ExecutionContext.Implicits.global
, который может быть изменен с помощью свойств системы, но по умолчанию использует ThreadPoolExecutor
с одним потоком на процессорное ядро.
Однако планирование - это другое дело. Для некоторых случаев использования вы можете предоставить свой собственный ExecutionContext
, который всегда применял ту же самую задержку перед выполнением. Но если вы хотите, чтобы задержка была контролируемой с сайта вызова, то, конечно, вы не можете использовать Future.apply
, так как нет параметров для связи, как это должно быть запланировано. В этом случае я предлагаю отправлять задания непосредственно запланированному исполнителю.
Ответ 2
Анджей ответ уже охватывает большую часть земли в вашем вопросе. Стоит упомянуть, что Scala "неявный" контекст выполнения (import scala.concurrent.ExecutionContext.Implicits._
) буквально равен java.util.concurrent.Executor
, а вся концепция ExecutionContext - очень тонкая оболочка, но тесно связана с каркасом исполнителей Java.
Для достижения чего-то похожего на запланированные фьючерсы, как указывает Маурицио, вам придется использовать promises и любой механизм планирования третьей стороны.
Не имея общего механизма для этого встроенного в фьючерсы Scala 2.10, жаль, но ничего смертельного.
Обещание - это дескриптор для асинхронного вычисления. Вы создаете один (предполагая ExecutionContext
в области), вызывая val p = Promise[Int]()
. Мы просто пообещали целое число.
Клиенты могут получить будущее, которое зависит от выполняемого обещания, просто позвонив p.future
, который является всего лишь Scala будущим.
Выполнение обещания - это просто вызов p.successful(3)
, и в этот момент будущее будет завершено.
Play 2.x решает планирование с помощью promises и простого старого таймера Java 1.4.
Здесь - ссылка на источник ссылки.
Давайте также взглянем на источник здесь:
object Promise {
private val timer = new java.util.Timer()
def timeout[A](message: => A, duration: Long, unit: TimeUnit = TimeUnit.MILLISECONDS)
(implicit ec: ExecutionContext): Future[A] = {
val p = Promise[A]()
timer.schedule(new java.util.TimerTask {
def run() {
p.completeWith(Future(message)(ec))
}
}, unit.toMillis(duration))
p.future
}
}
Затем это можно использовать так:
val future3 = Promise.timeout(3, 10000) // will complete after 10 seconds
Обратите внимание, что это намного лучше, чем подключить Thread.sleep(10000)
к вашему коду, который заблокирует ваш поток и заставит контекстный переключатель.
Также стоит отметить в этом примере val p = Promise...
в начале функции и p.future
в конце. Это общий шаблон при работе с promises. Предположим, что эта функция делает некоторые обещания клиенту и запускает асинхронное вычисление для его выполнения.
Посмотрите здесь для получения дополнительной информации о Scala promises. Обратите внимание, что они используют строчный future
метод из объекта пакета concurrent
вместо Future.apply
. Первый просто делегирует последнее. Лично я предпочитаю строчный future
.