Kotlin:使用非阻塞I / O阻止协同程序

时间:2017-04-02 02:01:59

标签: multithreading kotlin coroutine

我正在尝试使用Kotlin协程来处理非阻塞I / O.情景如下:

  1. 从在线程1上运行的异步回调接收数据。
  2. 在线程2中等待此数据,然后使用它。
  3. 我当前的代码看起来像这样(为简洁起见而简化):

    private var latch = CountDownLatch(1)
    private var data: Any? = null
    
    // Async callback from non-blocking I/O
    fun onReceive(data: Any) {
        currentData = data
        latch.countDown()
    }
    
    // Wait and consume data
    fun getData(): Any? {
        latch.await()
        latch = CountDownLatch(1)
        return currentData
    }
    
    fun processData() {
        launch(CommonPool) {
            while (true) {
                val data = getData()
                // Consume data                
            }
        }
    }
    

    根据我的理解,Kotlin协程应该能够帮助我摆脱CountDownLatch。阅读this (awesome) guide后,我能想到的就是这样:

    // Wait and consume data
    fun getData() = async(CommonPool) {
        latch.await()
        latch = CountDownLatch(1)
        currentData
    }
    
    fun processData() {
        launch(CommonPool) {
            while (true) {
                runBlocking {
                    val data = getData().await()
                    // Consume data                
                }
            }
        }
    }
    

    我也尝试了Pipelines,结果相似。我显然不明白如何使用这些功能。

2 个答案:

答案 0 :(得分:1)

您没有说过onReceive()中收到的数据是否可以并行处理。这是主要问题。如果是,您可以在onReceive()中执行此操作。如果不允许这样做,请让onReceive()的每次调用都在CommonPool上启动任务,而不需要任何协同程序。如果它们应该按顺序处理,那么最简单的方法是启动一个内部循环的线程:

fun onReceive(data: Any) {
   queue.put(data);
}

 ....

// loop in a thread
while(true) {
   data = queue.take();
   processData(data);
}

同样,不需要协同程序。

通常,协同程序是表示异步程序的语法糖,就好像它是同步的一样。我不认为你的程序是使用协同程序的情况。

答案 1 :(得分:0)

在使用Android开发时,有一种非常普遍的模式使用CountDownLatch,有时您想在处理BroadcastReceivers时使异步实现同步,而CountDownLatch很方便。 / p>

private suspend fun yourSuspendMethod() {
    val job = GlobalScope.async {    
        val latch = CountDownLatch(1)

        val watcher = object : BroadcastReceiver() {

            override fun onReceive(context: Context?, intent: Intent?) {
                if // your logic
                    latch.countDown()
            }
        }

        try {
            mContext?.registerReceiver(watcher, IntentFilter(...))

            //call a method that will trigger the broadcast receiver

            if (!latch.await(5, TimeUnit.SECONDS)) {
                throw Exception("Failed .... on latch's timeout")
            }
        } finally {
            mContext?.unregisterReceiver(watcher)
        }
    }

    job.await()
}

这里有一件非常重要的事情,不要更改CoroutineScope的上下文,否则它们将在完全不同的地方运行,方法是按照我在上面留下的方式成为合并范围/上下文的子元素。 / p>

[编辑] 我决定对这个问题进行更多的考虑,以避免使用CountDownLatch。闩锁的问题是,当您调用latch.await时,它将停止当前线程,因此,如果该线程来自主线程,则主线程将等待并超时,因为它没有机会接收方被调用。解决此问题的一种方法是使用我上面使用的示例。

我在上面的示例中忘记的一件事是,如果您要进行单元测试并同步调用方的上下文,则需要注入上下文。如果您决定这样做,您的实现将变得更加复杂,并且您将无法使用协程的全部功能,因为您将创建额外的线程。

因此,解决方案将使用withTimeout + suspendCancellableCoroutine的组合,您可以使用以下扩展名:

suspend inline fun <T> suspendCoroutineWithTimeout(
    timeout: Long,
    crossinline block: (Continuation<T>) -> Unit
) = withTimeout(timeout) {
    suspendCancellableCoroutine(block = block)
}

,您的方法将如下所示:

private suspend fun yourSuspendMethod() {
    var watcher: BroadcastReceiver? = null

    try {
        suspendCoroutineWithTimeout<Boolean>(TimeUnit.SECONDS.toMillis(5)) {
            watcher = object : BroadcastReceiver() {

                override fun onReceive(context: Context?, intent: Intent?) {
                    if // your logic
                        it.resume(true)
                }
            }

            context?.registerReceiver(watcher, IntentFilter(...))
            //call a method that will trigger the broadcast receiver
        }
    } finally {
        context?.unregisterReceiver(watcher)
    }
}

就是这样。现在,协程将在不停止调用程序线程的情况下发挥作用,并且当作业被取消时,超时也将取消。