【问题标题】:Suspended function to read from InputStream从 InputStream 中读取的挂起函数
【发布时间】:2019-01-10 01:05:09
【问题描述】:

我对协程还很陌生,因此我想征求意见。

我创建了一个扩展函数来从InputStream 读取数据:

suspend fun InputStream.readData(): ByteArray {
    return withContext(Dispatchers.IO) {
        while (available() == 0) {
            delay(10)
        }
        val count = available()
        val buffer = ByteArray(count)
        read(buffer, 0, count)
        return@withContext buffer
    }
}

您认为从协程的角度来看,我有什么可以改进的吗?

【问题讨论】:

    标签: kotlin kotlin-coroutines


    【解决方案1】:
    while (available() == 0) {
        delay(10)
    }
    

    在这里您希望您已经使用InputStream 实现了非阻塞IO。您想象数据会以某种方式自行“涓涓细流”,您可以等待它变得可用,这样您就可以在随后的read() 调用中获取它而不会阻塞。

    这种行为对任何InputStream 都不是通用的。事实上,它可能只适用于SocketInputStream,并且它也存在问题:当远程端关闭连接时,它将继续返回0,直到您再次调用read 以观察套接字已关闭.

    InputStream 的其他实现中,available() 将始终返回 0,除非流被缓冲,在这种情况下,它只会告诉您缓冲区中还剩下多少。当缓冲区为空时,输入流实现不会尝试从底层资源获取更多数据,直到您调用read()

    因此,我建议至少将函数的接收者缩小到SocketInputStream,但为了完全正确,您应该改用 NIO 代码。

    最后,如果您发现对于您的特定用例,available() 循环确实按预期工作并且read() 从不阻塞,那么您应该放弃withContext(IO),因为它意味着两个代价高昂的上下文切换(到后台线程并返回),其目的只是在 GUI 线程之外运行阻塞代码。

    【讨论】:

    • 我发布的代码用于从套接字读取。 read() 操作的问题在于它阻塞并且无法取消。我将在withTimeout 子句中使用发布的代码,所以这就是delay(10) 的原因。
    • 据我所知,available() 机制无论如何都是useless。它从未对我有用,我确实尝试了几次。您应该使用非阻塞网络 IO 并在 Main 调度程序中运行。
    • 我认为这完全取决于人们想如何使用inputStream。就我而言,我将阅读直到找到我感兴趣的信息(或者阅读将超时)。在套接字关闭之前不会读取。另外,根据文档,available() 返回不会阻止读取操作的数字。
    【解决方案2】:

    从协程的角度来看,您的代码似乎还可以,没有什么可改进的。只需从协程构建器中调用该函数:launch - 如果您想要并发或 async - 如果您想要并行。例如:

    yourScope.launch {
    
        val inputStream = BufferedInputStream(FileInputStream("filename"))
        val result = inputStream.use {
            it.readData()
        }
    
        // use ByteArray result
    }
    

    另外,您可以稍微减少代码,将 return@withContext buffer 替换为 buffer 并将 withContext(Dispatchers.IO) 移出函数块:

    suspend fun InputStream.readData(): ByteArray = withContext(Dispatchers.IO) {
        while (available() == 0) {
            delay(10)
        }
        val count = available()
        val buffer = ByteArray(count)
        read(buffer, 0, count)
        buffer
    }
    

    【讨论】:

      【解决方案3】:

      除了 Marko 的回答,我想指出,您不能仅通过使用协程将阻塞代码变为非阻塞代码这一事实并不意味着您不应该使用协程。为了获得其他好处而使用它们是有意义的:

      1. 它可以保持异步代码的顺序样式。如果您有几个步骤来完成整个任务,则不需要使用特殊的反应类型及其组合器。
      2. 提供了一种扩展任务执行的好方法。在多个任务的情况下,您可以在多个上下文中由不同的调度程序结构化地执行它们。

      希望这有助于理解整个画面。

      【讨论】:

        猜你喜欢
        • 2012-03-03
        • 1970-01-01
        • 2017-07-15
        • 2018-06-26
        • 1970-01-01
        • 2010-12-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多