设为首页 加入收藏

TOP

[转] Scala 中的异步事件处理(四)
2019-08-15 00:11:22 】 浏览:198
Tags:Scala 异步 事件 处理
的任务代码。

清单 9. 结合使用 future 与 async {}

def runAsync(): Future[Int] = {
  async {
    val v1 = await(task1(1))
    val a = task2(v1)
    val b = task3(v1)
    await(task4(await(a) + await(b)))
  }
}

清单 9 中封装的 async {...} 调用了 async 宏。此调用将该代码块声明为异步执行的代码,并在默认情况下异步执行它,然后返回一个 future 表示该代码块的执行结果。在该代码块中,await() 方法(实际上是该宏的一个关键字,而不是一个真正的方法)显示了何处需要一个 future 的结果。async 宏在编译期间修改了 Scala 程序的抽象语法树 (AST),以便将该代码块转换为使用回调的代码,这大体相当于 清单 4 的代码。

除了 async {...} 包装器之外,清单 9 中的代码还与 清单 3 中最初的阻塞代码很相似。这主要是这个宏的成就,它抽象化了异步事件的所有复杂性,使它看起来像您在编写简单的线性代码。在幕后,这涉及到大量复杂性。

async 内部原理

如果查看 Scala 编译器从源代码生成的类,就会看到一些具有类似 AsyncHappy$$anonfun$1.class 的名称的内部类。从名称可以猜到,这些类由编译器为异步函数而生成(比如传递给 onSuccess() 或 flatMap() 方法的语句。)

使用 Scala 2.11.1 编译器和 Async 0.9.2 实现,您还会看到一个名为 AsyncUnhappy$stateMachine$macro$1$1.class 的类。这是 async 宏生成的实际实现代码,采用状态机的形式来处理异步任务。清单 10 给出了此类的一个部分地方进行了反编译(decompiled)的视图。

清单 10. 反编译后的 AsyncUnhappy$stateMachine$macro$1$1.class

public class AsyncUnhappy$stateMachine$macro$1$1
  implements Function1<Try<Object>, BoxedUnit>, Function0.mcV.sp
{
  private int state;
  private final Promise<Object> result;
  private int await$macro$3$macro$13;
  private int await$macro$7$macro$14;
  private int await$macro$5$macro$15;
  private int await$macro$11$macro$16;
  ...
  public void resume() {
    ...
  }
 
  public void apply(Try<Object> tr) {
    int i = this.state;
    switch (i) {
      default:
        throw new MatchError(BoxesRunTime.boxToInteger(i));
      case 3:
        if (tr.isFailure()) {
          result().complete(tr);
        } else {
          this.await$macro$11$macro$16 = BoxesRunTime.unboxToInt(tr.get());
          this.state = 4;
          resume();
        }
        break;
      case 2:
        if (tr.isFailure()) {
          result().complete(tr);
        } else {
          this.await$macro$7$macro$14 = BoxesRunTime.unboxToInt(tr.get());
          this.state = 3;
          resume();
        }
        break;
      case 1:
        if (tr.isFailure()) {
          result().complete(tr);
        } else {
          this.await$macro$5$macro$15 = BoxesRunTime.unboxToInt(tr.get());
          this.state = 2;
          resume();
        }
        break;
      case 0:
        if (tr.isFailure()) {
          result().complete(tr);
        } else {
          this.await$macro$3$macro$13 = BoxesRunTime.unboxToInt(tr.get());
          this.state = 1;
          resume();
        }
        break;
    }
  } 
  ...
}

清单 10 中的 apply() 方法处理实际的状态更改,估算一个 future 的结果并将输出状态更改为匹配。输入状态会告诉该代码正在估算哪个 future;每个状态值对应于 async 代码块中一个特定的 future。从 清单 10 的部分代码很难了解这一点,但查看其他一些字节码,就可以看到状态代码是与任务匹配的,所以状态 0 表示 task1 的结果符合预期,状态 1 表示 task2 的结果符合预期,依此类推。

resume() 方法并未显示在 清单 10 中,因为反编译器无法确定如何将它转换为 Java 代码。我也不打算探讨这个过程,但通过查看字节码,可以确定 resume() 方法执行了与状态代码上的 Java switch 相似的工作。对于每个非最终状态,resume() 执行适当的代码段来设置下一个预期的 future,最终将 AsyncUnhappy$stateMachine$macro$1$1 实例设置为 future 的 onComplete() 方法的目标。对于最终状态,resume() 将会设置结果值并履行对最终结果的承诺。

您实际上并不需要深入分析生成的代码来理解 async(但它可能很有趣)。关于 async 工作原理的完整描述,请查阅 SIP-22 - Async 提案。

async 限制
由于 async 宏将代码转换为状态机类的方式,该宏的使用有一些限制。最明显的限制是,不能将 await() 嵌套在 async 代码块中的另一个对象或闭包内(包括一个函数定义)。也不能将 await() 嵌套在一个 try 或 catch 内。

除了这些使用限制之外,async 的最大问题是:在调试时,您同样会体验到一些通常与异步代码有关的问题回调,在这种情况下,需要尝试理解没有反映明显的代码结构的调用堆栈。不幸的是,目前的调试器设计无法解决这些问题。这是 Scala 中一个新的工作区域(请参阅 反思调试器。)与此同时,您可以禁用 async 代码块的异步执行,让调试变得更轻松(假设您尝试修复的问题在按顺序执行操作时仍然存在)。

最后,Scala 宏仍是一项我们正在开展的工作。async 有望在未来的版本中成为 Scala 语言的一个正式部分,但只有在 Scala 语言团队对宏的工作方式感到满意时,这种情况才会出现。到那时,无法确保 async 的格式不会发生改变。

结束语

一些处理异步事件的 Scala 方法与 Java 代码存在很大的区别。借助 flatMap() 和 async 宏,Scala 提供了整洁而且容易理解的技术。async 特别有趣,您可以编写看似正常的顺序的代码,但编译的代码会并发地执行。Scala 不是提供这种方法的惟一语言,但基于宏的实现为其他方法提供了极高的灵活性。

本文转自:http

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

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目