使用回调编写两个Scala期货,没有第三个ExecutionContext

时间:2017-05-05 23:49:26

标签: scala future

我有两种方法,让他们称呼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 = ...

现在:

  1. 我不在意哪个先完成
  2. 我不关心执行其他计算的线程,除了:
  3. 我不想阻止任何一个线程等待另一个;
  4. 我不想阻止第三个(调用)线程;和
  5. 我不想只是为了设置标志而开始第四个线程。
  6. 如果我使用for-comprehensions,我会得到类似的东西:

    val loading = load()
    val initialization = initialize()
    
    for {
      loaded <- loading
      initialized <- initialization
    } yield { onBothComplete() }
    

    我得无法找到隐含的ExecutionContext。

    我认为这意味着Scala想要第四个线程等待两个期货的完成并设置标志,可以是明确的新ExecutionContextExecutionContext.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,它们都能够在执行原始线程的线程上运行回调工作

    更新了,以澄清阻止任何一个线程等待另一个线程也是不可接受的。

    再次更新,以减少对完成后计算的具体说明。

2 个答案:

答案 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-1pool-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()
})