[英]How do Scala Futures operate on threads? And how can they be used to execute async & non-blocking code?
To my understanding, there are the 3 ways of doing IO in Scala, which I will try to express in pesudo code.据我了解,Scala 中有 3 种执行 IO 的方式,我将尝试用伪代码来表达。
First , synchronous & blocking:一、同步&阻塞:
val syncAndBlocking: HttpResponse = someHttpClient.get("foo.com")
Here the main thread is just idle until the response is back..这里主线程只是空闲,直到响应回来..
Second , async but still blocking:其次,异步但仍然阻塞:
val asyncButBlocking: Future[HttpResponse] = Future { someHttpClient.get("bar.com") }
To my understanding, here the main thread is free (as Future executes on a separate thread) but that separate thread is blocked..据我了解,这里的主线程是空闲的(因为 Future 在一个单独的线程上执行)但是那个单独的线程被阻塞了..
Third , asynchronous & non blocking.第三,异步和非阻塞。 I am not sure how to implement that one, but to my best guess the implementation (eg. http client) itself has to be non-blocking, so something like:
我不确定如何实现那个,但据我所知,实现(例如 http 客户端)本身必须是非阻塞的,例如:
val asynAndNotBlocking: Future[HttpResponse] = Future { someNonBlockingHttpClient.get("baz.com") }
My questions are:我的问题是:
Question inspired by the following references: here & here受以下参考资料启发的问题:此处和此处
val syncAndBlocking: HttpResponse = someHttpClient.get("foo.com")
This will block the calling thread until the response is received (as you note).这将阻塞调用线程,直到收到响应(如您所见)。
val asyncButBlocking: Future[HttpResponse] = Future { someHttpClient.get("bar.com") }
As with any call to Future.apply
, this (at least conceptually, there may be optimizations which eliminate some steps):与对
Future.apply
任何调用Future.apply
,这(至少在概念上,可能会进行优化以消除某些步骤):
Function0[HttpResponse]
(I'll call it a thunk, for brevity) whose apply
method is someHttpClient.get("bar.com")
.Function0[HttpResponse]
(为了简洁,我将其称为 thunk),其apply
方法是someHttpClient.get("bar.com")
。 If someHttpClient
is static, this could theoretically happen at compile time (I don't know off the top of my head if the Scala compiler will perform this optimization), otherwise it will happen at runtime.someHttpClient
是静态的,理论上这可能在编译时发生(我不知道 Scala 编译器是否会执行此优化),否则它将在运行时发生。Future.apply
, which then:Future.apply
,然后:Promise[HttpResponse]
.Promise[HttpResponse]
。ExecutionContext
passed implicitly to Future.apply
.Future.apply
的ExecutionContext
上安排任务。 This task is to call the thunk: if the thunk successfully executes, the Promise
's associated Future
is completed (successfully) with the result of the thunk, otherwise that Future
fails (also a completion) with the thrown Throwable
(it may only fail if the Throwable
is matched by NonFatal
, I'm too lazy to check, in which case fatal throws are caught by the ExecutionContext
).Promise
的关联Future
就用thunk的结果完成(成功),否则Future
失败(也是一个完成),抛出的Throwable
(可能只会失败)如果Throwable
与NonFatal
匹配,我懒得检查,在这种情况下, ExecutionContext
会捕获致命的抛出)。ExecutionContext
(which may or may not be before the thunk is executed), the Future
associated with the Promise
is returned.ExecutionContext
上安排了任务(可能在也可能不在 thunk 执行之前),就会返回与Promise
关联的Future
。 The particulars of how the thunk is executed depend on the particular ExecutionContext
and by extension on the particulars of the Scala runtime (for Scala on the JVM, the thunk will be run on a thread determined by the ExecutionContext
, whether this is an OS thread or a green thread depends on the JVM, but OS thread is probably a safe assumption at least for now (Project Loom may affect that); for ScalaJS (since JavaScript doesn't expose threads) and Scala Native (as far as I know for now: conceivably an ExecutionContext
could use OS threads, but there would be risks in the runtime around things like GC), this is probably an event loop with a global thread).如何执行 thunk 的细节取决于特定的
ExecutionContext
以及 Scala 运行时的细节(对于 JVM 上的 Scala,thunk 将在ExecutionContext
确定的线程上ExecutionContext
,无论这是操作系统线程还是绿色线程取决于 JVM,但 OS 线程至少目前可能是一个安全的假设(Project Loom 可能会影响到这一点);对于 ScalaJS(因为 JavaScript 不公开线程)和 Scala Native(据我目前所知) :可以想象,一个ExecutionContext
可以使用操作系统线程,但是在运行时会存在诸如 GC 之类的风险),这可能是一个具有全局线程的事件循环)。
The calling thread is blocked until step 5 has executed, so from the caller's perspective, it's non-blocking, but there's a thread somewhere which is blocked.调用线程被阻塞,直到第 5 步执行完毕,所以从调用者的角度来看,它是非阻塞的,但是某处有一个线程被阻塞。
val asynAndNotBlocking: Future[HttpResponse] = Future { someNonBlockingHttpClient.get("baz.com") }
...is probably not going to typecheck (assuming that it's the same HttpResponse
type as above) since in order to be non-blocking the HttpResponse
would have to be wrapped in a type which denotes asynchronicity/non-blocking (eg Future
), so asyncAndNotBlocking
is of type Future[Future[HttpResponse]]
, which is kind of a pointless type outside of a few specific usecases. ...可能不会进行类型检查(假设它与上面的
HttpResponse
类型相同),因为为了非阻塞, HttpResponse
必须包装在表示异步/非阻塞的类型中(例如Future
),所以asyncAndNotBlocking
是Future[Future[HttpResponse]]
类型,在一些特定用例之外,这是一种毫无意义的类型。 You'd be more likely to have something like:你更有可能有这样的事情:
val asyncAndNotBlocking: Future[HttpResponse] = someNonBlockingHttpClient.get("baz.com")
or, if someNonBlockingHttpClient
isn't native to Scala and returns some other asynchrony library, you'd have或者,如果
someNonBlockingHttpClient
不是 Scala 原生的并返回一些其他异步库,您将拥有
val asyncAndNotBlocking: Future[HttpResponse] = SomeConversionToFuture(someNonBlockingHttpClient.get("baz.com"))
SomeConversionToFuture
would basically be like the sketch above of Future.apply
, but could, instead of using an ExecutionContext
use operations in that other asynchrony library to run code to complete the associated Future
when .get
completes. SomeConversionToFuture
基本上就像上面的Future.apply
草图,但可以,而不是使用ExecutionContext
使用其他异步库中的操作来运行代码以在.get
完成时完成关联的Future
。
If you really wanted Future[Future[HttpResponse]]
for some reason, given that it's likely that someNonBlockingHttpClient
will return very quickly from .get
(remember, it's asynchronous, so it can return as early as the request being set up and scheduled for being sent), Future.apply
is probably not the way to go about things: the overhead of steps 1-5 may take longer than the time spent in .get
!如果出于某种原因你真的想要
Future[Future[HttpResponse]]
,考虑到someNonBlockingHttpClient
很可能会从.get
非常快地返回(记住,它是异步的,所以它可以在请求被设置和安排的时候尽早返回发送), Future.apply
可能不是解决问题的方法:步骤 1-5 的开销可能比.get
花费的时间更长! For this sort of situation, Future.successful
is useful:对于这种情况,
Future.successful
很有用:
val doubleWrapped: Future[Future[HttpResponse]] = Future.successful( someNonBlockingHttpClient.get("baz.com"))
Future.successful
doesn't involve a thunk, create a Promise
, or schedule a task on the ExecutionContext
(it doesn't even use an ExecutionContext
!). Future.successful
不涉及 thunk、创建Promise
或在ExecutionContext
上安排任务(它甚至不使用ExecutionContext
!)。 It directly constructs an already-successfully-completed Future
, but the value contained in that Future
is computed (ie what would be in the thunk is executed) before Future.successful
is called and it blocks the calling thread.它直接构造了一个已经成功完成的
Future
,但是在Future.successful
被调用之前计算了包含在该Future
的值(即执行 thunk 中的值)并且它阻塞了调用线程。 This isn't a problem if the code in question is blocking for just long enough to setup something to execute asynchronously, but it can make something that's blocking for a long time look to the outside world like it's async/non-blocking.如果有问题的代码阻塞了足够长的时间来设置异步执行,这不是问题,但是它可以使长时间阻塞的东西看起来像异步/非阻塞一样。
Knowing when to use Future.apply
and Future.successful
is of some importance, especially if you care about performance and scalability.知道什么时候使用
Future.apply
和Future.successful
是很重要的,特别是如果你关心性能和可伸缩性。 It's probably more common to see Future.successful
used inappropriately than Future.apply
(because it doesn't require an implicit ExecutionContext
, I've seen novices gravitate to it).看到
Future.successful
使用不当可能比Future.apply
更常见(因为它不需要隐式ExecutionContext
,我见过新手被它Future.apply
)。 As Colin Breck put it, don't block your future success by improperly using Future.successful
.正如 Colin Breck 所说, 不要通过不当使用
Future.successful
阻碍您未来的成功。
A Future doesn't ”execute“ anywhere, because it is not a piece of runnable code – it is merely a way to access a result that may or may not yet be available. Future 不会在任何地方“执行”,因为它不是一段可运行的代码——它只是一种访问可能可用或可能不可用的结果的方法。
The way you create a Future
is by creating a Promise
and then calling the future
method on it.创建
Future
的方式是创建一个Promise
,然后在其上调用future
方法。 The Future will be completed as soon as you call the complete
method on the Promise object.一旦调用 Promise 对象的
complete
方法,Future 就会完成。
When you create a Future using Future { doStuff() }
, what happens is that a Promise is created, then doStuff
starts executing in the ExecutionContext
– usually that means it's running in a different thread.当你使用
Future { doStuff() }
创建一个 Future 时,会发生一个 Promise 被创建,然后doStuff
开始在ExecutionContext
——通常这意味着它在不同的线程中运行。 Then .future
is called on the Promise and the Future is returned.然后在 Promise 上调用
.future
并返回 Future。 When doStuff
is done, complete
is called on the Promise.当
doStuff
完成时,在 Promise 上调用complete
。
So conceptually, Futures and threads/ExecutionContexts are independent – there doesn't need to be a thread (green or otherwise) doing stuff for every incomplete future.所以从概念上讲,Futures 和线程/ExecutionContexts 是独立的——不需要有一个线程(绿色或其他)为每个不完整的 Future 做事。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.