1. 等待方式:
1.1 Await 同步等待
本文刚才所提到的 Await 是一种同步等待机制,主线程会在有限的时间内等待某个 Future 进行。
我们另引入一个包:scala.concurrent.duration._,这样就允许我们使用 2 second 这种方式来表示我们的最大等待时间了(笔者曾经在隐式转换章节中介绍过如何实现它)。
Await 主要有两个方法。第一个用法是调用 result 另主线程进入阻塞等待,直到获取该 future 的返回值。
val intFuture = Future {
println("正在计算...")
println("执行此计算任务的线程是:" + Thread.currentThread().getName)
Thread.sleep(1000)
30
}
//主程序会在 3 秒内等待该结果,并赋值。
val int : Int = Await.result(intFuture,3 second)
println(int)
一般用于需要获取到该 future 的返回值才能做进一步操作的情况,如果只关心该 future 的完成状态,可以调用 ready 方法。当 future 仍处于工作状态时,主线程会等待至多 3 秒。
Await.ready(intFuture, 3 second)
另外,通过 Thread.currentThread().getName 可以发现,此 future 是由另一个线程执行的:ForkJoinPool-X-worker-XX 。
1.2 onComplete 异步等待
忠告:如果你已经进入了 Future 空间内,就尽量不要再使用 Await 阻塞 future 的执行。Scala 提供注册 “回调函数” 的方式来令你通过函数副作用获取到某个 future 在未来返回的值。
val intFuture = Future {
println("正在计算...")
println("执行此计算任务的线程是:" + Thread.currentThread().getName)
Thread.sleep(1000)
30
}
// Await.ready(intFuture, 3 second)
// 和刚才的情况不同,如果主线程不阻塞一会,那么这个程序会提前结束推出。
Thread.sleep(3000)
var intValue : Int = 0
intFuture onComplete {
case Success(value) =>
println(value)
// 通过代码块副作用获取到这个 Future 的 value 返回值。
intValue = value
case _ => println("出现了意外的错误")
}
这种方式不会阻塞主线程,为了能看到程序运行结果,我们需要主动调用 Thread.sleep 让主线程休眠一会,否则程序会立刻结束。onComplete 的返回值是一个 Unit 数据类型。
1.3 使用 andThen 强制保证 future 的执行顺序
一个 future 可以绑定多个 onComplete 。然而,上下文环境并不会保证哪个 future 的 onComplete 会被率先触发,而 andThen 方法保证了回调函数的执行顺序。
import scala.concurrent.ExecutionContext.Implicits.global
val intFuture = Future {
Thread.sleep(2000)
println(Thread.currentThread().getName)
200
}
// 主程序的 onComplete 方法的调用顺序不一定
intFuture onComplete {
case Success(int) => println(s"this future returned $int")
case _ => println("something wrong has happened.")
}
intFuture onComplete {
case Success(int) => println(s"completed with the value of $int")
case _ => println("something wrong has happened.")
}
Thread.sleep(3000)
执行上述的程序,控制台有可能先打印 this future returned $int ,也有可能先打印 completed with the value of $int 。
import scala.concurrent.ExecutionContext.Implicits.global
val intFuture = Future {
Thread.sleep(2000)
println(Thread.currentThread().getName)
200
}
intFuture onComplete {
case Success(int) => println(s"this future returned $int")
case _ => println("something wrong has happened.")
}
intFuture andThen {
case Success(int) => println(s"completed with the value of $int")
case _ => println("something wrong has happened.")
}
Thread.sleep(3000)
andThen 方法会返回原 future 的一个镜像,并且只会在该 future 调用完 onCompelete 方法之后,andThen 才会执行。
Promise
当我们不确定 future 何时会完成时,可以会借助 Promise 许下一个 “承诺” ,它表示:在某个未来的时间点,一定能够得到值。
val promisedInt: Promise[Int] = Promise[Int]
复制代码
然而,这个 Int 值的计算实际上委托给了其它的 future 来完成。受托的 Future 在计算完结果之后会调用该 promise 的 success 方法来 “兑现” 这个承诺。
val intFuture = Future {
println(“正在计算…”)
println(“执行此计算任务的线程是:” + Thread.currentThread().getName)
Thread.sleep(1000)
//一旦这样做,这个 promise 将和当前的 future 绑定。
promisedInt.success(300)
}
复制代码
考虑到异常情况,除了 success 方法, Promise 还提供了 failure , Complete 等方法。无论调用哪种方法,一个 Promise 都只能被使用一次。
promisedInt.success(300)
// promisedInt.failure(new Exception(“可能的错误”))
// promisedInt.complete(Success(1))
复制代码
随后此 promise 的 future 会进入就绪状态,我们使用刚才介绍的 onComplete 回调函数中 “兑现” 它的返回值。
promisedInt.future onComplete {
case Success(value) => println(value)
case _ => println(“出现了意外的错误”)
}
复制代码
PromisedInt 在这里充当着代理的作用。它承诺提供的值具体要由哪个 future 来计算并提供,程序的调用者可能并不关心:它也许是 intFuture ,也许是 IntFuture2 。因此,我们仅需要为代理( PromisedInt.future )设置回调函数,而不是其它的 future。为了方便理解,这里给出连贯的代码清单:
import scala.concurrent.ExecutionContext.Implicits.global
val promisedInt: Promise[Int] = Promise[Int]
val intFuture = Future {
println(“正在计算…”)
println(“执行此计算任务的线程是:” + Thread.currentThread().getName)
Thread.sleep(1000)
// promisedInt 承诺的值由 intFuture 真正实现。
promisedInt.success(300)
promisedInt.failure(new Exception(“可能的错误”))
promisedInt.complete(Success(1))
}
// 和刚才的情况不同,如果主线程不阻塞一会,那么这个程序会提前结束退出。
Thread.sleep(3000)
// 主函数只关心 promisedInt 能否提供值。
promisedInt.future onComplete {
case Success(value) => println(value)
case _ => println(“出现了意外的错误”)
}
复制代码
过滤 Future 的返回值
Scala 提供两种方式让你对 future 的返回值进行检查,或者过滤。filter 方法可以对 future 的结果进行检验。如果该值合法,就进行保留。下面的例子使用 filter 确保返回值是满足 >=30 的值。注意,执行 filter 方法之后得到的是另一个 future 值。
import scala.concurrent.ExecutionContext.Implicits.global
val eventualInt = Future {
Thread.sleep(3000)
print(s"${Thread.currentThread().getName} : return result.")
12
}
// 检查返回值是否 >= 30 .
val checkRes: Future[Int] = eventualInt filter(_ >= 30)
// 阻塞等待
while (!checkRes.isCompleted){
Thread.sleep(1000)
println(“waiting…”)
}
// 注册回调。
checkRes onComplete {
case Success(res) =>
println(s"result : $res")
case Failure(cause) =>
println(s"failed because of KaTeX parse error: Expected 'EOF', got '}' at position 9: cause") }̲ 复制代码 如果不满足匹配的要…{Thread.currentThread().getName} : return result.")
22
}
// 检查返回值是否 >= 30 . 采取不同的策略。
val transformRes: Future[Int] = eventualInt collect {
case res : Int if res > 30 => res + 30
case res : Int if res > 20 => res + 20
}
while (!transformRes.isCompleted){
Thread.sleep(1000)
println(“waiting…”)
}
transformRes onComplete {
case Success(int) => println(s"value of $int")
case Failure(cause) => println(s"failed because of $cause")
}
复制代码
处理失败的预期
failed 方法
Scala 提供了几种处理失败的 future 的方式:包含 failed , fallbackTo , recover 和 recoverWith 。举例:如果某个 future 在执行时出现异常,则 failed 方法会返回一个成功的 Future[Throwable] 实例。
import scala.concurrent.ExecutionContext.Implicits.global
val intFuture = Future {10 / 0}
intFuture onComplete {
case Success(int) => println(int)
case Failure(cause) => println(s"failed because of $cause")
}
val eventualThrowable: Future[Throwable] = intFuture failed
//Some(Success(java.lang.ArithmeticException: /by zero))
println(eventualThrowable.value)
复制代码
如果 future 是被正常执行的,则 failed 方法反而会抛出 NoSuchElement 。
fallbackTo 方法
fallbackTo 方法提供了保险机制,它允许原始的 future 失败时,转而去运行另一个 future2 。
val intFuture = Future {10 / 0}
intFuture onComplete {
case Success(int) => println(“intFuture” + int)
case Failure(cause) => println(s"failed because of $cause")
}
val maybeFailed: Future[Int] = intFuture fallbackTo Future {100}
maybeFailed onComplete {
case Success(int) => println(“maybeFailed” + int)
case _ => println(“This future’s throwable will be ignored.”)
}
Thread.sleep(2000)
println(maybeFailed.value)
复制代码
无论 intFuture 执行是否成功,maybeFailed 也总是会运行(笔者亲测),因此不要在这里设置一些具有副作用的代码。当 intFuture 运行成功时,maybeFailed 的返回值将被会忽略,它实际返回的是 intFuture 的返回值。在 intFuture 运行失败的情况下, maybeFailed 方法的返回值才会生效。
如果 maybeFailed 在执行时也出现了异常,则它抛出的异常将被忽略,捕捉到的将是上一级 intFuture 的原始异常。
作者:花花子
链接:https://juejin.cn/post/6994661689102696456
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。