设为首页 加入收藏

TOP

[转] Scala 中的异步事件处理(二)
2019-08-15 00:11:22 】 浏览:200
Tags:Scala 异步 事件 处理
uture 联系在一起的方式,以便按正确顺序并使用正确的依赖关系执行任务,而不使用阻塞。

清单 4. 使用 onSuccess() 处理事件的完成

def runOnSuccess() = {
  val result = Promise[Int]
  task1(1).onSuccess(v => v match {
    case v1 => {
      val a = task2(v1)
      val b = task3(v1)
      a.onSuccess(v => v match {
        case v2 =>
          b.onSuccess(v => v match {
            case v3 => task4(v2 + v3).onSuccess(v4 => v4 match {
              case x => result.success(x)
            })
          })
      })
    }
  })
  result.future
}

清单 4 代码使用 onSuccess() 方法将一个函数(技术上讲是一个部分函数,因为它仅处理成功完成的情况)设置为在每个 future 完成时返回。因为 onSuccess() 调用是嵌套式的,所以它们将按顺序执行(即使 future 未完全按顺序完成)。

清单 4 的代码比较容易理解,但很冗长。清单 5 展示了一种使用 flatMap() 方法处理这种情况的更简单的方法。

清单 5. 使用 flatMap() 处理事件的完成

def runFlatMap() = {
  task1(1) flatMap {v1 =>
    val a = task2(v1)
    val b = task3(v1)
    a flatMap { v2 =>
      b flatMap { v3 => task4(v2 + v3) }}
  }
}

清单 5 中的代码实际上执行了与 清单 4 相同的事情,但 清单 5 使用了 flatMap() 方法从每个 future 中提取单一结果值。使用 flatMap() 消除了 清单 4 中所需的 match / case 结构,提供了一种更简洁的格式,但采用了同样的逐步执行路线。

试用示例

示例代码使用了一个 Scala App 来依次运行事件代码的每个版本,并确保完成事件(约 5 秒)和结果 (13) 是正确的。您可以使用 Maven 从命令行运行此代码,如清单 6 所示(删除了无关的 Maven 输出):

清单 6. 运行事件代码

dennis@linux-9qea:~/devworks/scala4/code> mvn scala:run -Dlauncher=happypath
...
[INFO] launcher 'happypath' selected => com.sosnoski.concur.article4.AsyncHappy
Starting runBlocking
runBlocking returned 13 in 5029 ms.
Starting runOnSuccess
runOnSuccess returned 13 in 5011 ms.
Starting runFlatMap
runFlatMap returned 13 in 5002 ms.

不顺利的道路

目前为止,您看到了以 future 形式协调事件的代码,这些代码总是能够成功完成。在真实应用程序中,不能寄希望于事情总是这么顺利。处理任务过程中可能会出现问题,而且在 JVM 语言术语中,这些问题通常表示为 Throwable。

更改 清单 2 中的任务定义很容易,只需使用 delayedFailure() 代替 delayedSuccess() 方法,如这里的 task4 所示:

def task4(input: Int) = TimedEvent.delayedFailure(1, "This won't work!")

如果运行仅将 task4 修改为完成时抛出异常的 清单 3,那么您会得到 task4 上的 Await.result() 调用所抛出的预期的 IllegalArgumentException。如果在 runBlocking() 方法中没有捕获该问题,该异常会在调用链中一直传递,直到最终捕获问题(如果未捕获问题,则会终止线程)。幸运的是,修改该代码很容易,因此,如果任何任务完成时抛出异常,该异常会通过返回的 future 传递给调用方来处理。清单 7 展示了这一更改。

清单 7. 具有异常的阻塞等待

def runBlocking() = {
  val result = Promise[Int]
  try {
    val v1 = Await.result(task1(1), Duration.Inf)
    val future2 = task2(v1)
    val future3 = task3(v1)
    val v2 = Await.result(future2, Duration.Inf)
    val v3 = Await.result(future3, Duration.Inf)
    val v4 = Await.result(task4(v2 + v3), Duration.Inf)
    result.success(v4)
  } catch {
    case t: Throwable => result.failure(t)
  }
  result.future
}

清单 7 非常浅显易懂,最初的代码包装在一个 try/catch 中,catch 在返回的 future 完成时传回异常。此方法稍微复杂一些,但任何 Scala 开发人员应该仍然很容易理解它。

那么,清单 4 和清单 5 中的事件处理代码的非阻塞变形是怎样的?从名称可以看出,清单 4 中使用的 onSuccess() 方法仅 适用于 future 的成功完成类型。如果想要同时处理成功和失败完成类型,则必须使用 onComplete() 方法,检查哪种完成例行适用。清单 8 展示了此技术如何用在事件处理代码中。

清单 8. 成功和失败的 onComplete() 处理

def runOnComplete() = {
  val result = Promise[Int]
  task1(1).onComplete(v => v match {
    case Success(v1) => {
      val a = task2(v1)
      val b = task3(v1)
      a.onComplete(v => v match {
        case Success(v2) =>
          b.onComplete(v => v match {
            case Success(v3) => task4(v2 + v3).onComplete(v4 => v4 match {
              case Success(x) => result.success(x)
              case Failure(t) => result.failure(t)
            })
            case Failure(t) => result.failure(t)
          })
        case Failure(t) => result.failure(t)
      })
    }
    case Failure(t) => result.failure(t)
  })
  result.future
}

清单 8 看起来很凌乱,幸运的是还有一种简单得多的替代方法:使用 清单 5 中的 flatMap() 代码代替。flatMap() 方法同时处理成功和失败完成类型,无需执行任何更改。

使用 async

最新的 Scala 版本包含在编译期间使用宏 转换代码的能力。目前实现的一个最有用的宏是 async,它在编译期间将使用 future 的看似顺序的代码转换为异步代码。清单 9 展示了 async 如何简化本教程中使用

首页 上一页 1 2 3 4 下一页 尾页 2/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Scala环境搭建及Intellij IDEA安装 下一篇Scala模式匹配常用

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目