Scala笔记–期货– 1

几乎所有现代编程语言都有用于并行编程的Future-Promise习语。 我不想让您无聊为什么我们需要更高级别的并发抽象。 取而代之的是,在本文中,我们将继续进行讨论,仅讨论Scala的期货方法。

scala.concurrent.Future是尚未实现的值的表示。 该值通常是更长和/或并行计算的结果。

粗略地说,当包装在Future时,同步运行的代码块将异步运行。 Future所有实现都为我们提供了一个句柄,通过该句柄,我们可以获取该块计算的结果值(或它的意义!)。

在本文中,我们将介绍如何构造Future并通过阻塞等待和回调从中提取值的基础。 在下一部分中,我们将讨论组成Futures和其他高级构造,例如用于异常处理的recoverfallback

创建异步计算

创建使用Future异步运行的计算非常容易。 我们只需要将我们的逻辑纳入Futureapply功能中

val aFuture: Future[Int] = Future {  
    //Some massively huge super important computation
}

例如,让我们创建一个oneFuture ,它在延迟一秒钟后返回1。

val oneFuture: Future[Int] = Future {  
    Thread.sleep(1000)
    1
}

让我们暂停并探索apply功能

def apply[T](body: =>T)(implicit executor: ExecutionContext): scala.concurrent.Future[T]

对。 你让我在那里。 该方法还接受称为ExecutionContext作为隐式参数。 当使用代码块构造Future时,计算将在ExecutionContextExecutionContextExecutionContextThreadPool同义词,当启动Future时,它将在单独的线程上运行。

Scala在scala.concurrent.ExecutionContext.global提供了现成的静态全局ExecutionContext,我们现在打算使用它。 (稍后对此有更多介绍)

因此,整个代码实际上看起来像这样

class PromisingFutures{

  import scala.concurrent.ExecutionContext.Implicits.global

  val oneFuture: Future[Int] = Future {
    Thread.sleep(1000)
    1
  }
  ...
  ...
关于使用SCALA.CONCURRENT.EXECUTIONCONTEXT.GLOBAL

global ExecutionContext使用起来非常方便。 但是,基础ThreadPool是ForkJoinPool 。 毫无疑问,ForkJoinPool对于短暂的计算而言是惊人的,但是强烈建议不要对诸如数据库或Web服务调用之类的Blocking IO(或者甚至是长时间运行的计算或只是在JVM之外进行交谈)进行阻止。

好消息是,我们仍然可以使用Future,但解决方法是仅使用一个不是ForkJoinPool的单独线程池-例如,固定的ThreadPool是一个不错的选择。

implicit lazy val fixedThreadPoolExecutionContext: ExecutionContext = {  
    val fixedThreadPool: ExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors * 2) //or some fixed number
    ExecutionContext.fromExecutor(fixedThreadPool)
}

未来的状态和价值

在开始从Future中提取值之前,让我们看一下Future的各种状态是什么,何时以及在这些状态中实际可用的值是什么。

未来只有两个状态-未完成和已完成。

考虑下图

FutureStatesAndValues

当进行计算时,我们可以从Future中获得的结果值是Option[Try[T]]

  1. 在“ 未完成”状态下,尚未实现计算结果。 因此,该值将为None
  2. 完成后 ,结果为Some(Try[T]) ,这意味着它可能是以下之一:
    1. 计算的积极结果或
    2. 一个例外

我们来看一个Success案例

def checkState(): Unit = {  
    println("Before the job finishes")
    Thread.sleep(500)
    println(s"Completed : ${oneFuture.isCompleted}, Value : ${oneFuture.value}")

    println("After the job finishes")
    Thread.sleep(1100)
    println(s"Completed : ${oneFuture.isCompleted}, Value : ${oneFuture.value}")

}

delayStringFuture Future完成所需的时间为1秒。 因此,我们首先在500毫秒后检查Future是否已完成并打印其当前值。 Futurevalue函数返回一个Option[Try[T]] 。 毫不奇怪,对于isCompleted检查,我们得到一个错误;对于值本身,我们得到一个None

输出500毫秒:

Before the job finishes  
Completed : false, Value : None

让我们在1100毫秒后再次进行检查,以使1000毫秒的睡眠后有一些余地。 现在的输出是计算本身的结果,并且完成状态现在为true。

1100毫秒的输出:

After the job finishes  
Completed : true, Value : Some(Success(1))

现在我们已经解决了这个问题,让我们看看如何从Future中提取价值

从未来中提取价值

除了组成期货(我们将在下周看到)之外,还有两种方法可以从期货中提取价值:阻止等待和回调

1.使用AWAIT.RESULT阻止等待:

scala.concurrent.Await的结果函数具有以下语法:

@throws(classOf[Exception])
    def result[T](awaitable: Awaitable[T], atMost: Duration): T =
      blocking(awaitable.result(atMost)(AwaitPermission))

它接受Future就是Awaitable特征的实现。 它还接受第二个参数atMost ,该参数指示调用者线程必须阻塞该结果的最长时间。 在atMost持续时间到期时,如果Future仍未完成,则将抛出java.util.concurrent.TimeoutException

如果Future完成,则Await.result将提取我们的实际值。 如果Future完成并且Future的结果是Throwable,则将异常传播到调用方。

不鼓励在生产代码中使用Await.result ,但是此结构对于运行针对Future的测试用例非常有用。

让我们考虑两个期货,原始的oneFuture和引发异常的oneDangerousFuture

它们的外观如下:

val oneFuture: Future[Int] = Future {
    Thread.sleep(1000)
    1
  }

  val oneDangerousFuture=Future{
    Thread.sleep(2000)
    throw new SomeComputationException("Welcome to the Dark side !")
  }

  case class SomeComputationException(msg: String) extends Exception(msg)
测试用例

这里有三个测试用例:

  1. 第一个是我们的快乐时光场景–计算需要1秒钟,我们等待结果的最大持续时间为2秒。 我们显然会取得结果。 我们关于应该返回值的主张变成了事实。
  2. 在第二个测试用例中,我们生成一个Future并抛出SomeComputationException ,并且断言当我们等待结果时,异常会传播到调用方。
  3. 在最后一个测试用例中,我们仅等待500毫秒,而计算本身需要1秒。 从Await.result的实现中可以看到,此调用引发TimeoutException
class PromisingFutureTest extends FunSpec with Matchers {

  describe("A PromisingFuture") {

    it("should hold a Int value if the Await.result is called after the Future completes") {
      val promisingFuture = new PromisingFutures()
      val oneFuture = promisingFuture.oneFuture //Takes 1 second to compute
      val intValue = Await.result(oneFuture, 2 seconds)
      intValue should be(1)
    }

    it("should propagate the Exception to the callee if the computation threw an exception") {
      val promisingFuture = new PromisingFutures()
      val oneDangerousFuture = promisingFuture.oneDangerousFuture //throws exception
      intercept[SomeComputationException] {
        val intValue = Await.result(oneDangerousFuture, 2 seconds)
      }
    }

    it("should throw a TimeOutException exception when an Await.result's atMost parameter is lesser than the time taken for the Future to complete") {
      val promisingFuture = new PromisingFutures()
      val oneDelayedFuture = promisingFuture.oneFuture //Takes 1 second to compute
      intercept[TimeoutException] {
        Await.result(oneDelayedFuture, 500 millis)
      }
    }
  }

}

2.回调:

Future提取值(而不是编写)的替代方法和干净方法是通过回调。 在Future上可以使用三种不同的回调onSuccessonFailure和组合的onComplete

  1. 仅当Future成功完成结果后,才调用onSuccess回调。
  2. 仅在发生异常时才调用onFailure回调。
  3. onCompleteonSuccessonFailure的组合。 在展开围绕Future的结果的Option后,它接受在Try[T]上运行的函数。
def onComplete[U](f: Try[T] => U)(implicit executor: ExecutionContext): Unit

请注意,所有回调都返回一个Unit ,这意味着它们不能被组合并且具有副作用。

现在让我们看看如何使用onComplete回调。 我有一个名为printFuture小方法,一旦完成,它只会将Future的内容写入控制台。 让我们尝试将oneFutureoneDangerousFuture传入其中。

class PromisingFutures {  
...
...
def printFuture[T](future: Future[T]): Unit = future.onComplete {  
    case Success(result) => println(s"Success $result")
    case Failure(throwable) => println(s"Failure $throwable")
}
...
object PromisingFutures{

 def main(args: Array[String]) {
    val promisingFutures=new PromisingFutures
    promisingFutures.printFuture(promisingFutures.oneFuture)
    promisingFutures.printFuture(promisingFutures.oneDangerousFuture)

    synchronized(wait(3000))
  }
}

输出:

Success 1  
Failure SomeComputationException: Welcome to the Dark side !

如预期的那样, oneFuture进入Success案例并产生1,而oneDangerousFuture进入Failure案例并输出Exception:

时报

Scala提供了一种非常方便的类似DSL的语法来表示TimeUnit。 2秒,5分钟等。三个隐式类scala.concurrent.duration包– DurationIntDurationLongDurationDouble以及trait DurationConversions都具有这种魔力。 我们需要做的就是导入scala.concurrent.duration._

同样,在我们的示例中,“ 500毫秒”可以表示为“ 500毫秒”,“ 500毫秒”或“ 500毫秒”。 各种时间单位的别名的完整列表可以在特征DurationConversions的源代码或Scaladoc中找到。

支持此博客的完整代码可在github中找到

翻译自: https://www.javacodegeeks.com/2016/06/scala-notes-futures-1.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值