我有两种方法,让他们称呼load()
和init()
。每个人都在自己的线程中开始计算,并在自己的执行上下文中返回Future
。这两个计算是独立的。
val loadContext = ExecutionContext.fromExecutor(...)
def load(): Future[Unit] = {
Future
}
val initContext = ExecutionContext.fromExecutor(...)
def init(): Future[Unit] = {
Future { ... }(initContext)
}
我想从第三个帖子中调用这两个帖子 - 从main()
说出来 - 并在完成两个时执行其他计算。
def onBothComplete(): Unit = ...
现在:
如果我使用for-comprehensions,我会得到类似的东西:
val loading = load()
val initialization = initialize()
for {
loaded <- loading
initialized <- initialization
} yield { onBothComplete() }
我得无法找到隐含的ExecutionContext。
我认为这意味着Scala想要第四个线程等待两个期货的完成并设置标志,可以是明确的新ExecutionContext
或ExecutionContext.Implicits.global
。因此,似乎理解力已经消失了。
我以为我可以嵌套回调:
initialization.onComplete {
case Success(_) =>
loading.onComplete {
case Success(_) => onBothComplete()
case Failure(t) => log.error("Unable to load", t)
}
case Failure(t) => log.error("Unable to initialize", t)
}
不幸的是onComplete
也采用隐式ExecutionContext
,我得到了同样的错误。 (这也很丑陋,如果loading
失败,则会从initialization
丢失错误消息。)
有没有办法在没有阻止和没有引入另一个ExecutionContext
的情况下编写Scala Futures?如果没有,我可能不得不将它们扔到Java 8 CompletableFutures或 Javaslang Vavr Futures,它们都能够在执行原始线程的线程上运行回调工作
更新了,以澄清阻止任何一个线程等待另一个线程也是不可接受的。
再次更新,以减少对完成后计算的具体说明。
答案 0 :(得分:1)
为什么不重用一个自己的执行上下文?不确定你对这些的要求是什么,但是如果你使用单个线程执行器,你可以只重用那个作为你的理解的执行上下文,你将不会创建任何新的线程:
implicit val loadContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor)
如果你真的无法重用它们,你可以将其视为隐式执行上下文:
implicit val currentThreadExecutionContext = ExecutionContext.fromExecutor(
(runnable: Runnable) => {
runnable.run()
})
将在当前线程上运行期货。但是,Scala文档明确建议不要这样做,因为它引入了非确定性,其中线程运行Future
(但正如您所说,您不关心它运行的线程,因此这可能无关紧要。)
请参阅Synchronous Execution Context了解为何这不可行。
上下文的一个例子:
val loadContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor)
def load(): Future[Unit] = {
Future(println("loading thread " + Thread.currentThread().getName))(loadContext)
}
val initContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor)
def init(): Future[Unit] = {
Future(println("init thread " + Thread.currentThread().getName))(initContext)
}
val doneFlag = new AtomicBoolean(false)
val loading = load()
val initialization = init()
implicit val currentThreadExecutionContext = ExecutionContext.fromExecutor(
(runnable: Runnable) => {
runnable.run()
})
for {
loaded <- loading
initialized <- initialization
} yield {
println("yield thread " + Thread.currentThread().getName)
doneFlag.set(true)
}
打印:
loading thread pool-1-thread-1
init thread pool-2-thread-1
yield thread main
虽然yield
行可能会打印pool-1-thread-1
或pool-2-thread-1
,具体取决于运行。
答案 1 :(得分:1)
在Scala中,Future
代表要执行异步的工作(即与其他工作单元同时执行)。 ExecutionContext
表示用于执行Future
的线程池。换句话说,ExecutionContext
是执行实际工作的工作团队。
为了提高效率和可扩展性,拥有大型团队(例如,单个ExecutionContext
有10个线程来执行10个Future
)而不是小团队会更好(例如5 ExecutionContext
,每个{2}个线程执行10 Future
&#39; s。
在您的情况下,如果您想将线程数限制为2,您可以:
def load()(implicit teamOfWorkers: ExecutionContext): Future[Unit] = {
Future { ... } /* will use the teamOfWorkers implicitly */
}
def init()(implicit teamOfWorkers: ExecutionContext): Future[Unit] = {
Future { ... } /* will use the teamOfWorkers implicitly */
}
implicit val bigTeamOfWorkers = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(2))
/* All async works in the following will use
the same bigTeamOfWorkers implicitly and works will be shared by
the 2 workers (i.e. thread) in the team */
for {
loaded <- loading
initialized <- initialization
} yield doneFlag.set(true)
无法找到隐式ExecutionContext 错误并不意味着Scala需要额外的线程。这只意味着Scala需要ExecutionContext
来完成工作。而额外的ExecutionContext
并不一定意味着额外的&#39;线程&#39;以下ExecutionContext
,而不是创建新线程,将在当前线程中执行工作:
val currThreadExecutor = ExecutionContext.fromExecutor(new Executor {
override def execute(command: Runnable): Unit = command.run()
})