使用scala continuation与netty / NIO侦听器

时间:2012-02-07 23:18:04

标签: scala netty continuations

我正在使用Netty库(GitHub的第4版)。它在Scala中运行良好,但我希望我的库能够使用延续传递样式进行异步等待。

传统上使用Netty你会做这样的事情(例如异步连接操作):

//client is a ClientBootstrap
val future:ChannelFuture = client.connect(remoteAddr);
future.addListener(new ChannelFutureListener {
    def operationComplete (f:ChannelFuture) = {
        //here goes the code that happens when the connection is made   
    }
})

如果你正在实现一个库(我是),那么你基本上有三个简单的选项,允许库的用户在建立连接后做东西:

  1. 只需从您的connect方法返回ChannelFuture并让用户处理它 - 这不会提供很多来自netty的抽象。
  2. 将ChannelFutureListener作为connect方法的参数,并将其作为监听器添加到ChannelFuture。
  3. 将一个回调函数对象作为connect方法的参数,并从你创建的ChannelFutureListener中调用它(这将使得回调驱动的样式有点像node.js)。
  4. 我想做的是第四种选择;我没有将它包括在上面的计数中,因为它并不简单。

    我想使用scala分隔的continuation来使库的使用有点像阻塞库,但它将在幕后无阻塞:

    class MyLibraryClient {
        def connect(remoteAddr:SocketAddress) = {
            shift { retrn: (Unit => Unit) => {
                    val future:ChannelFuture = client.connect(remoteAddr);
                    future.addListener(new ChannelFutureListener {
                        def operationComplete(f:ChannelFuture) = {
                            retrn();
                        }   
                    });
                }
            }   
        }
    }
    

    想象一下,其他读/写操作以相同的方式实现。这样做的目的是用户的代码看起来更像这样:

    reset {
        val conn = new MyLibraryClient();
        conn.connect(new InetSocketAddress("127.0.0.1", 1337));
        println("This will happen after the connection is finished");
    }
    

    换句话说,该程序看起来像一个简单的阻塞式程序,但在幕后不会有任何阻塞或线程。

    我遇到的麻烦是我不完全理解分隔连续的输入是如何工作的。当我尝试以上述方式实现它时,编译器抱怨我的operationComplete实现实际上返回Unit @scala.util.continuations.cpsParam[Unit,Unit => Unit]而不是Unit。我得知scala的CPS中有一些“陷阱”,你必须使用shift注释@suspendable方法的返回类型,它会在调用堆栈中传递到reset,但是似乎没有任何方法可以与已经存在的没有分隔连续概念的Java库进行协调。

    我觉得真的必须有办法解决这个问题 - 如果Swarm可以序列化连续并将它们阻塞在网络上以便在其他地方进行计算,那么必须可以简单地从预先存在的Java类调用延续。但我无法弄清楚它是如何完成的。我是否必须在Scala中重写netty的所有部分才能实现这一目标?

1 个答案:

答案 0 :(得分:4)

当我开始时,我发现Scala's continuations的这个解释非常有帮助。特别要注意他解释shift[A, B, C]reset[B, C]的部分。添加虚拟null作为operationComplete的最后一个语句应该会有所帮助。

顺便说一下,如果retrn()内嵌reset,你需要在另一个shift内调用import scala.util.continuations._ import java.util.concurrent.Executors object Test { val execService = Executors.newFixedThreadPool(2) def main(args: Array[String]): Unit = { reset { val conn = new MyLibraryClient(); conn.connect("127.0.0.1"); println("This will happen after the connection is finished"); } println("Outside reset"); } } class ChannelFuture { def addListener(listener: ChannelFutureListener): Unit = { val future = this Test.execService.submit(new Runnable { def run(): Unit = { listener.operationComplete(future) } }) } } trait ChannelFutureListener { def operationComplete(f: ChannelFuture): Unit } class MyLibraryClient { def connect(remoteAddr: String): Unit@cps[Unit] = { shift { retrn: (Unit => Unit) => { val future: ChannelFuture = new ChannelFuture() future.addListener(new ChannelFutureListener { def operationComplete(f: ChannelFuture): Unit = { println("operationComplete starts") retrn(); null } }); } } } }

编辑:这是一个工作示例

Outside reset
operationComplete starts
This will happen after the connection is finished

可能的输出:

{{1}}