Scala异步/等待和并行化

Scala异步/等待和并行化,scala,asynchronous,future,async-await,Scala,Asynchronous,Future,Async Await,我正在学习Scala中async/await的用法。我读过这本书 理论上,此代码是异步的(非阻塞),但它不是并行的: def slowCalcFuture: Future[Int] = ... def combined: Future[Int] = async { await(slowCalcFuture) + await(slowCalcFuture) } val x: Int = Await.result(combined, 10

我正在学习Scala中async/await的用法。我读过这本书

理论上,此代码是异步的(非阻塞),但它不是并行的:

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)
}
而另一个是并行的:

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)
}
它们之间唯一的区别是使用了中间变量。
这会如何影响并行化?

因为它类似于C#中的
async&wait
,也许我可以提供一些见解。在C#中,通常的规则是,可以等待的
任务
应返回“hot”,即已在运行。我假设Scala中也是如此,从函数返回的
Future
不必显式启动,只需在被调用后“运行”。如果不是这样的话,那么下面是纯粹的(可能不是真的)猜测

让我们分析第一个案例:

async {
    await(slowCalcFuture) + await(slowCalcFuture)
}
我们到达那个街区,开始第一次等待:

async {
    await(slowCalcFuture) + await(slowCalcFuture)
    ^^^^^
}
好的,我们正在异步地等待计算完成。完成后,我们“继续”分析块:

async {
    await(slowCalcFuture) + await(slowCalcFuture)
                            ^^^^^
}
第二次等待,所以我们异步地等待第二次计算完成。完成后,我们可以通过添加两个整数来计算最终结果

正如您所见,我们正在一步一步地等待,等待未来,因为它们一个接一个地到来

让我们看一下第二个示例:

async {
  val future1 = slowCalcFuture
  val future2 = slowCalcFuture
  await(future1) + await(future2)
}
好的,下面是(可能)发生的情况:

然后我们正在等待第一个
未来
,但这两个未来目前都在运行。当第一个返回时,第二个可能已经完成(因此我们将立即获得结果),或者我们可能需要等待更长的时间


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

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

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

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

val future1=slowcalcfurture
wait(slowcalcfurture)
之间的根本区别,就如同让别人给你煮咖啡和等你的咖啡准备好一样。提问需要2秒钟:这句话的意思是:“请给我煮咖啡好吗?”。但等待咖啡准备好需要4分钟

此任务的可能修改可能正在等待第一个可用答案。例如,您希望连接到群集中的任何服务器。您发出连接到您知道的每台服务器的请求,第一个响应的将是您的服务器。您可以通过以下方式完成此操作:
Future.firstCompletedOf(数组(slowCalcFuture,slowCalcFuture))

非常有用的解释。现在我明白了。谢谢!:-)你的答案是我真的想知道我什么时候读过关于
async/await
的文档,但是没有一个提到
实际上并没有阻止执行线程的事情