【问题标题】:suspend IO function never return挂起 IO 函数永不返回
【发布时间】:2022-01-02 11:42:17
【问题描述】:

我在为 Android 编写 UDP 消息接收循环时遇到了困难。

在下面的代码中,在receiveLoop 中,对receiveMessages 的调用永远不会返回,因此我永远不会进入消息处理循环。 请注意,我仍然可以接收数据包,但是当通道缓冲区已满时它会停止。

我希望receiveMessages 立即返回,而其中的阻塞 IO 循环仍将永远运行。

class MySocketUDP(private val params: SocketParams) {


    private val rcvSocket: DatagramSocket by lazy {
        val sock = DatagramSocket(params.rcvPort)
        sock.reuseAddress = true
        sock.soTimeout = 1000
        sock
    }

    suspend fun receiveMessages(channel: SendChannel<Message>) {
        withContext(Dispatchers.IO) {
            val buf = ByteArray(MAX_MSG_SIZE)
            while (true) {
                val pkt = DatagramPacket(buf, buf.size)
                try {
                    if (channel.isClosedForSend) {
                        break
                    }
                    rcvSocket.receive(pkt)
                    val msg = packetToMessage(buf, 0, pkt.length)
                    Log.d("SOCKET", "filling channel with $msg")
                    channel.send(msg)
                } catch (ex: SocketTimeoutException) {
                } catch (ex: CancellationException) {
                    break
                }
            }
        }
    }
}

class MyModel {

    private suspend fun receiveLoop(socket: MySocketUDP) {
        withContext(Dispatchers.Main) {
            val channel = Channel<Message>(16)
            socket.receiveMessages(channel)
            Log.d("MODEL", "Entering msg loop")
            for (msg in channel) {
                dispatchRcvMessage(msg)
            }
        }
    }

}

  1. 为什么receiveMessagesIO 调度程序中运行并从Main 调度程序调用时从不返回?
  2. 我是否需要为此类生产者/消费者工作实际生成一个线程?
  3. 您能否展示如何以“协程友好”的方式很好地实现如此长的阻塞代码?

谢谢

【问题讨论】:

  • 为什么你期望receiveMessages 不等待循环结束就返回?挂起函数是同步的,因此它们与任何其他函数一样,仅在完成执行其主体时才返回。广告。 2:是和不是。你需要生成一个新的协程,而不是线程。
  • @broot 我误解了withContext的作用。我认为如果新上下文与调用上下文不同,它必然会创建一个异步调用。

标签: android kotlin kotlin-coroutines blocking coroutinescope


【解决方案1】:
  1. receiveMessages() 是一个挂起函数,它调用另一个挂起函数withContext(),这又是一个无限循环。所以调用socket.receiveMessages(channel)会在循环未完成时暂停代码执行。

  2. 您需要为消费者和生产者启动单独的协程,例如使用launch 函数。

  3. 使用协程的一些例子:

     val someScope = CoroutineScope(Dispatchers.Main)
    
     private suspend fun receiveLoop(socket: MySocketUDP) = someScope.launch {
         val channel = Channel<Message>(16)
         socket.receiveMessages(channel)
         Log.d("MODEL", "Entering msg loop")
         for (msg in channel) {
             dispatchRcvMessage(msg)
         }
     }
    
     // In MySocketUDP
     suspend fun receiveMessages(channel: SendChannel<Message>) {
         someAnotherScope.launch { // or can use coroutineScope builder function
             val buf = ByteArray(MAX_MSG_SIZE)
             while (true) {
                 val pkt = DatagramPacket(buf, buf.size)
                 try {
                     if (channel.isClosedForSend) {
                         break
                     }
                     rcvSocket.receive(pkt)
                     val msg = packetToMessage(buf, 0, pkt.length)
                     Log.d("SOCKET", "filling channel with $msg")
                     channel.send(msg)
                 } catch (ex: SocketTimeoutException) {
                 } catch (ex: CancellationException) {
                     break
                 }
             }
         }
     }
    

【讨论】:

  • 谢谢!我想我误解了withContextlaunch 相比的作用。我也不知道从头开始创建CoroutineScope 这么容易。据我了解,与例如相比,是什么带来了这个新的范围。 viewModelScope?
  • 您可以使用来自ViewModel 的内置viewModelScope,而无需创建新的CoroutineScope。如果不能使用内置作用域,则必须创建自己的作用域来启动协程。
猜你喜欢
  • 2019-03-23
  • 1970-01-01
  • 1970-01-01
  • 2020-05-13
  • 2014-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多