Scala异步/等待和并行化

时间:2013-11-20 09:32:46

标签: scala asynchronous future async-await

我正在学习Scala中async / await的用法。我在https://github.com/scala/async

中读过这篇文章

理论上这段代码是异步的(非阻塞的),但它没有并行化:

def slowCalcFuture: Future[Int] = ...             
def combined: Future[Int] = async {               
   await(slowCalcFuture) + await(slowCalcFuture)
}
val x: Int = Await.result(combined, 10.seconds)    

而另一个是并行化的:

def combined: Future[Int] = async {
  val future1 = slowCalcFuture
  val future2 = slowCalcFuture
  await(future1) + await(future2)
}

它们之间的唯一区别是使用中间变量。 这怎么会影响并行化?

3 个答案:

答案 0 :(得分:48)

因为它与C#中的async & await相似,所以我可以提供一些见解。在C#中,一般规则是可以等待的Task应该返回“热”,即已经运行。我假设它在Scala中是相同的,其中从函数返回的Future不必显式启动,而是在被调用后才“运行”。如果它的话,那么以下是纯粹的(也可能不是真的)推测。

让我们分析第一种情况:

async {
    await(slowCalcFuture) + await(slowCalcFuture)
}

我们到达那个街区并点击第一个等待:

async {
    await(slowCalcFuture) + await(slowCalcFuture)
    ^^^^^
}

好的,所以我们异步等待计算完成。当它完成后,我们继续“分析块:

async {
    await(slowCalcFuture) + await(slowCalcFuture)
                            ^^^^^
}

第二个等待,所以我们异步等待第二次计算完成。完成后,我们可以通过添加两个整数来计算最终结果。

正如您所看到的那样,我们正在逐步等待,等待Future一个接一个地出现。

让我们来看看第二个例子:

async {
  val future1 = slowCalcFuture
  val future2 = slowCalcFuture
  await(future1) + await(future2)
}

好的,所以这是(可能)发生的事情:

async {
  val future1 = slowCalcFuture // >> first future is started, but not awaited
  val future2 = slowCalcFuture // >> second future is started, but not awaited
  await(future1) + await(future2)
  ^^^^^
}

然后我们正在等待第一个Future,但两个期货当前正在运行。当第一个返回时,第二个可能已经完成(因此我们将立即获得结果)或者我们可能需要等待一段时间。

现在很明显,第二个示例并行运行两个计算,然后等待它们两个完成。两者都准备好后,它会返回。第一个示例以非阻塞方式运行计算,但是按顺序运行。

答案 1 :(得分:23)

如果有点难以理解,Patryk的答案是正确的。关于async / await的主要理解是它只是另一种做Future的{​​{1}}的方法。幕后没有并发魔法。异步块内的所有调用都是顺序的,包括await实际上不会阻塞正在执行的线程,而是将其余的异步块包装在一个闭包中并将其作为回调传递给它完成我们正在等待的flatMap。所以在第一段代码中,第二次计算直到第一次await完成才开始,因为没有人在此之前启动它。

答案 2 :(得分:0)

在第一种情况下,您创建一个新线程来执行缓慢的未来并在一次调用中等待它。因此,在第一个缓慢的未来完成后,将执行第二个缓慢未来的调用。

在第二种情况下调用val future1 = slowCalcFuture时,它会有效地创建一个新线程,将指向“slowCalcFuture”函数的指针传递给线程并说“请执行它”。从线程池中获取线程实例需要花费尽可能多的时间,并将指向函数的指针传递给线程实例。这可以算是即时的。因此,因为val future1 = slowCalcFuture被转换为“获取线程并传递指针”操作,所以它很快就会完成,并且下一行的执行没有任何延迟val future2 = slowCalcFuture。 Feauture 2计划也毫不拖延地执行。

val future1 = slowCalcFutureawait(slowCalcFuture)之间的基本区别与要求某人让你喝咖啡并等待咖啡准备就绪之间的区别相同。要求需要2秒钟:这需要说短语:“你可以让我喝咖啡吗?”。但等待咖啡准备就绪需要4分钟。

此任务的可能修改可能是等待第一个可用答案。例如,您要连接到群集中的任何服务器。您发出连接到您知道的每个服务器的请求,并且第一个响应的服务器将是您的服务器。你可以这样做: Future.firstCompletedOf(Array(slowCalcFuture, slowCalcFuture))