如何使用Java代码中的Kotlin协程实现NIO套接字(客户端)?

时间:2018-12-12 04:32:57

标签: android kotlin okhttp kotlinx.coroutines socketchannel

我想使用Kotlin(v1.3.0)协程和java.nio.channels。 SocketChannel (NIO)替换Socket connect(阻止IO )。因为这样可以节省许多线程。

以下代码无法运行,因为job.await()是Kotlin中的暂停函数,只能在Ktolin协程块中调用。像launch{..}async{..}

// this function will be called by Java Code
fun connect(address: InetSocketAddress, connectTimeout: Int): SocketChannel {

    // Start a new connection
    // Create a non-blocking socket channel
    val socketChannel = SocketChannel.open()
    socketChannel.configureBlocking(false)

    // async calls NIO connect function
    val job = GlobalScope.async(oneThreadCtx) {
        aConnect(socketChannel, address)
    }

    // I what to suspend(NOT block) current Java Thread, until connect is success
    job.await()

    return socketChannel
}

但是,我尝试使用runBlocking{..}将此功能设置为Java中的普通功能。但job.await阻止了当前的Java线程未暂停

那么,如何使用Kotlin(v1.3.0)协程实现此功能?

2 个答案:

答案 0 :(得分:2)

正如Marko所指出的那样,即使该阻塞操作在异步协程中,您的代码仍将最终阻塞一个线程。要真正获得Java和Kotlin所需的异步行为,您需要使用Socket Channel

的异步版本

这样,您将获得真正的异步套接字处理。使用该类和Kotlin的suspendCoroutine构建器方法,您可以将异步处理程序转换为可挂起的调用。

这里是实现阅读的示例:

class TcpSocket(private val socket: AsynchronousSocketChannel) {
    suspend fun read(buffer: ByteBuffer): Int {
        return socket.asyncRead(buffer)
    }

    fun close() {
        socket.close()
    }

    private suspend fun AsynchronousSocketChannel.asyncRead(buffer: ByteBuffer): Int {
        return suspendCoroutine { continuation ->
           this.read(buffer, continuation, ReadCompletionHandler)
        }
    }

    object ReadCompletionHandler : CompletionHandler<Int, Continuation<Int>> {
        override fun completed(result: Int, attachment: Continuation<Int>) {
            attachment.resume(result)
        }

        override fun failed(exc: Throwable, attachment: Continuation<Int>) {
            attachment.resumeWithException(exc)
        }
    }
}

您可以选择删除我在这里进行的包装,而只在AsynchronousSocketChannel上公开一个asyncRead方法,如下所示:

suspend fun AsynchronousSocketChannel.asyncRead(buffer: ByteBuffer): Int {
    return suspendCoroutine { continuation ->
       this.read(buffer, continuation, ReadCompletionHandler)
    }
}

object ReadCompletionHandler : CompletionHandler<Int, Continuation<Int>> {
    override fun completed(result: Int, attachment: Continuation<Int>) {
        attachment.resume(result)
    }

    override fun failed(exc: Throwable, attachment: Continuation<Int>) {
        attachment.resumeWithException(exc)
    }
}

这完全取决于口味以及您的设计目标是什么。您应该能够为初次连接实现类似的方法,就像我在这里阅读的那样。

答案 1 :(得分:0)

// I what to suspend(NOT block) current Java Thread, until connect is success
job.await()

这不是现实的期望。从Java的角度来看,suspend fun通过返回特殊常量COROUTINE_SUSPENDED暂停执行。您需要Kotlin编译器将其隐藏起来,并允许您编写可挂起的常规代码。

即使从Kotlin的角度来看,您的代码也没有非阻塞挂起,因为它使用阻塞调用进行连接。将该调用提交到另一个线程并不会使它成为非阻塞的。

您的代码所做的工作完全等同于将作业提交给Java执行服务,然后等待其结果。例如,您可以使用CompletableFuture.supplyAsync