前面讲过 Cloudopt Next 是基于 Vertx 的,而 Vertx 是一个类似 NodeJs 的 eventloop 框架,通过事件循环完成对网络请求的处理。所以 Vertx 不建议阻塞线程,例如数据库操作太慢等情况,尽可能通过异步进行操作。如果不可能避免的需要阻塞,请使用 Worker。

    EventLoop 线程模型

    3.0.0.0-BETA1 开始支持 Kotlin 协程及 await 语法。目前 Interceptor、 Validator、 Route 都已经支持协程(suspend 和 await)。Handler 没有支持打算,主要是因为 Handler 涉及到的地方很多,基本每一步都有,理论上在 Handler 中就不应该放阻塞的、处理时间长的代码,更适合做像是日志输出等等。如果需要使用阻塞代码建议使用下面这些专门运行阻塞代码的方法。

    同时 Next 对协程做了大量的优化封装。首先是对所有的类都注册了 worker 和 await 两个语法糖,你可以在任何的类中进行使用。在运行阻塞代码时会自动调用 vertx 的 executeBlocking() 来运行。

    请先引入 kotlin 协程相关的包。

    1. <dependency>
    2. <groupId>org.jetbrains.kotlinx</groupId>
    3. <artifactId>kotlinx-coroutines-core</artifactId>
    4. <version>1.4.3</version>
    5. </dependency>

    比如我们现在需要运行某个阻塞的代码并需要将结果传递回来,你可以用以下的方式运行。当然你也可以通过 Worker.worker 运行阻塞的代码。记得在结尾通过 handler.complete 传递结果,否则会陷入一直等待。

    1. fun test() {
    2. var id = worker<Int>({ handler ->
    3. handler.complete(1)
    4. }, { asyncResult ->
    5. //onComplete
    6. })
    7. }

    第二种做法是利用 kotlin 的协程的 await 特性,写起来会更为优雅。但利用 kotlin 协程需要在方法上声明 suspend。这也是我们推荐的异步写法。

    1. suspend fun test() {
    2. var id = await<Int> { handler ->
    3. handler.complete(1)
    4. }
    5. }

    第三种情况是你在无法支持 suspend 语法的地方使用 await 语法。你可以使用下面的方式运行:

    1. fun test() {
    2. global {
    3. var id = await<Int> { handler ->
    4. handler.complete(1)
    5. }
    6. }
    7. }

    global 语法本质上是使用 kotlin 的 GlobalScope,并自动将 NextServer 的 vertx 的协程对象配置进去。

    虽然使用了 suspend 声明,根据 kotlin 的语言规范是要求所有的调用上级都需要声明。本质上 suspend 在编译后的第一个参数是传递协程对象,所以必须一级一级往上声明,让最底下的方法拿到 suspend 对象。

    但是有些情况下,无法识别 suspend 语法,所以需要用其它办法将协程对象放进去。

    使用 global{} 语法糖包裹后,会自动把 vertx 的协程对象往下传递。那么上级方法就不需要声明 suspend 了。

    第四种做法是你可以在路由的方法上增加@Blocking注解,使其自动变为同步的、阻塞的路由。

    1. @Blocking
    2. @GET("blocking")
    3. fun blocking() {
    4. renderText("This is Blocking!")
    5. }

    第五种情况是,如果你要运行超过 10s 以上时间的代码,请不要使用上面的任意方法,建议使用 then 语法。

    3.0.0.0-BETA2 版本增加

    then 的语法糖会将包裹的代码放入上下文事件的队列中,会在上下文中所有的排队都处理完后才会被异步执行。

    1. then{
    2. println(1)
    3. }

    第六种情况是在 await 中直接返回。

    1. val text = await {
    2. return@await "success"
    3. }

    第七种情况是在不方便声明 suspend 的时候使用 async 方法体。

    async 方法体不能用在路由方法中,路由方法是在更上层被 launch 包裹的,async 是通过 runBlocking 实现的,这两个用在一起会冲突。在路由方法中及路由方法中调用的方法里请使用 global 进行包裹,或者直接使用 suspend 声明。

    1. private fun asyncFunction(): String = async {
    2. return@async await {
    3. return@await "success"
    4. }
    5. }

    Next 还内置了两种简单的定时器。第一个参数设置周期(单位为毫秒)、第二个参数设置是否循环、第三个参数设置回调。

    如果需要取消定时器需要凭着设置成功时的定时器 id 进行取消。

    1. fun test() {
    2. setTimer(1000,false) { id ->
    3. println("And one second later that is printed")
    4. }
    5. cancelTimer(id)
    6. }

    当然你也可以与上面协程的写法结合起来。

    1. suspend fun test() {
    2. val id = await<Long>{handler->
    3. setTimer(1000,false) { id ->
    4. println("And one second later that is printed")
    5. handler.complete(id)
    6. }
    7. }
    8. cancelTimer(id)
    9. }

    如果是在高性能、异步的场景上,数据插入要分成两种类型。一种是不可丢失必须立即存储的,另一种是可丢失不需要立即存储,要根据两种不同的情况分开处理。

    比如说在 Next 新版本中,不可丢失必须立即存储的可以使用 await 语法,可丢失不需要立即存储可以使用 then 语法或者 afterEvent 语法。then 和 afterEvent 会等到所有上下文都结束后才执行,保证了请求一定返回了。awaitEvent 的话是在整个容器中排队,then 则是会把整个上下文中所有用 then 包裹的代码块进行排队后执行。