Асинхронный IO в Scala с фьючерсами

Скажем, я получаю (потенциально большой) список изображений для загрузки с некоторых URL-адресов. Я использую Scala, поэтому я бы сделал следующее:

import scala.actors.Futures._

// Retrieve URLs from somewhere
val urls: List[String] = ...

// Download image (blocking operation)
val fimages: List[Future[...]] = urls.map (url => future { download url })

// Do something (display) when complete
fimages.foreach (_.foreach (display _))

Я немного новичок в Scala, поэтому для меня это все еще немного напоминает магию:

  • Это правильный способ сделать это? Любые альтернативы, если это не так?
  • Если у меня есть 100 изображений для загрузки, это создаст 100 потоков одновременно или будет использовать пул потоков?
  • Будет ли выполняться последняя команда (display _) в основном потоке, а если нет, как я могу убедиться, что она есть?

Спасибо за ваш совет!

Ответ 1

Использовать фьючерсы в Scala 2.10. Они были совместной работой между командой Scala, командой Akka и Twitter, чтобы достичь более стандартизированного будущего API и реализации для использования в рамках фреймворков. Мы только что опубликовали руководство по адресу: http://docs.scala-lang.org/overviews/core/futures.html

Помимо того, что он полностью не блокируется (по умолчанию, хотя мы предоставляем возможность выполнять управляемые операции блокировки) и скомпонован, фьючерсы Scala 2.10 поставляются с неявным пулом потоков для выполнения ваших задач, а также некоторыми утилитами для управлять тайм-аутами.

import scala.concurrent.{future, blocking, Future, Await, ExecutionContext.Implicits.global}
import scala.concurrent.duration._

// Retrieve URLs from somewhere
val urls: List[String] = ...

// Download image (blocking operation)
val imagesFuts: List[Future[...]] = urls.map {
  url => future { blocking { download url } }
}

// Do something (display) when complete
val futImages: Future[List[...]] = Future.sequence(imagesFuts)
Await.result(futImages, 10 seconds).foreach(display)

Выше мы сначала импортируем несколько вещей:

  • future: API для создания будущего.
  • blocking: API для управляемой блокировки.
  • future: будущий объект-компаньон, который содержит ряд полезных методов для коллекций фьючерсов.
  • Await: объект singleton, используемый для блокировки в будущем (передача его результата в текущий поток).
  • ExecutionContext.Implicits.global: пул потоков по умолчанию, пул ForkJoin.
  • duration._: утилиты для управления продолжительностью времени.

imagesFuts остается в значительной степени таким же, как и то, что вы изначально сделали - единственное отличие здесь в том, что мы используем управляемую блокировку blocking. Он уведомляет пул потоков о том, что блок кода, который вы передаете ему, содержит длительные или блокирующие операции. Это позволяет пулу временно создавать новых работников, чтобы не допустить, чтобы все рабочие были заблокированы. Это делается для предотвращения голодания (блокирования пула потоков) в блокирующих приложениях. Обратите внимание, что пул потоков также знает, когда код в блоке блокировки блокировки завершен, поэтому он удалит лишний рабочий поток в этой точке, а это означает, что пул будет уменьшаться до ожидаемого.

(Если вы хотите полностью предотвратить создание дополнительных потоков, то вы должны использовать библиотеку AsyncIO, такую ​​как библиотека Java NIO.)

Затем мы используем методы сбора объекта-компаньона Future для преобразования imagesFuts из List[Future[...]] в Future[List[...]].

Объект Await заключается в том, как мы можем гарантировать, что display выполняется в вызывающем потоке - Await.result просто заставляет текущий поток ждать, пока будущее, которое оно передано, будет завершено. (Это внутреннее использование управляемой блокировки.)

Ответ 2

val all = Future.traverse(urls){ url =>
  val f = future(download url) /*(downloadContext)*/
  f.onComplete(display)(displayContext)
  f
}
Await.result(all, ...)
  • Используйте scala.concurrent.Future в 2.10, теперь RC.
  • который использует неявный ExecutionContext
  • В документе New Future указано, что onComplete (и foreach) может сразу оценить, доступно ли значение. Старые актеры Будущее делает то же самое. В зависимости от вашего требования к отображению вы можете предоставить подходящий ExecutionContext (например, один исполнитель потока). Если вы просто хотите, чтобы основной поток дождался завершения загрузки, траверс дает вам будущее, на которое нужно ждать.

Ответ 3

  • Да, кажется мне хорошо, но вы можете изучить более мощные twitter-util или Akka Будущие API (Scala 2.10 будут иметь новую библиотеку Future в этом стиле).

  • Он использует пул потоков.

  • Нет, не будет. Для этого вам необходимо использовать стандартный механизм вашего инструментария GUI (SwingUtilities.invokeLater для Swing или Display.asyncExec для SWT). Например.

    fimages.foreach (_.foreach(im => SwingUtilities.invokeLater(new Runnable { display im })))