【问题标题】:Replace a Thread -- that has state variable -- with a Coroutine用协程替换具有状态变量的线程
【发布时间】:2021-11-09 17:34:36
【问题描述】:

为了重述标题,我想知道是否有办法将下面的MyThread 类转换为 Kotlin 协程。

如果您仔细观察,您会注意到MyThread 类有一个名为someObject 的属性变量,可以从runcancel 方法内部进行修改。在这种情况下,SomeObject 完全封装在 MyThread 中,我想保持这种状态。有没有办法将MyThread 转换为协程,还是我已经拥有最优雅的代码版本?

class MyCancellable: Thread(){
    val someObject= SomeObject()

    override fun run() {
        super.run()
        while(someObject.keepGoing){
            someObject.value++
        }
    }

    fun cancel(){
        someObject.keepGoing=false
    }
}

【问题讨论】:

  • “将其转换为协程没有任何优势”-> 除非您想要协程可以为您提供的许多功能,包括但不限于基于范围的生命周期感知取消。 ..
  • 是的,我认为我把这个问题误读为关于性能的问题。
  • 我要强调的一点是我是否能够通过另一种方法,即cancel()来修改完全封装的someObject。这是协程不明显的唯一原因,不是吗?否则,如果这个线程只包含一个run 方法,我可以很容易地用协程替换它。
  • 我无法理解您在此状态下的用例。看看你如何使用这个类会很有用。如果someObject 是共享的可变状态(看起来确实如此),那么可能有更好的方法来使用协程组织代码。请注意,只要您使主循环协作,您就可以使用协程本身的取消机制而无需像这样的手动工作
  • 好吧,假设someObjectServerSocket。在run 方法中,我将调用serverSocket.accept(),这是一个阻塞调用。在等待连接到网络时,我可能会出于多种原因改变主意,在这种情况下,我需要致电 serverSocket.close()

标签: multithreading kotlin kotlin-coroutines


【解决方案1】:

我想你想要一个可以启动自己的协程的类?这似乎是等价的,类似于:

class MyCancellable(private val scope: CoroutineScope) {
    private var job: Job? = null
    val someObject = SomeObject()

    fun run() {
        if (job != null) return
        job = scope.launch {
            while(someObject.keepGoing) {
                someObject.value++
            }
        }
    }

    fun cancel() {
        someObject.keepGoing = false
    }
}

通常你会改用 job.cancel(),并在 while 循环中检查 isActive - 我认为这在这里并不重要,但它可能值得“正确”执行(而且它在技术上与 @ 不同987654324@ 因其他原因变为假)。如果你这样做,也许 TenFour04 的建议会更好,因为你需要一个类/对象的唯一原因是你可以在其中放置外部可见的 runcancel 函数。如果协程只是runs,而你在Job 上调用cancel,它就会返回,这一切都很好!

【讨论】:

    【解决方案2】:

    可重用协程是一个suspend 函数,其中唯一的参数是CoroutineScope,所以大致相当于你所拥有的是:

    fun CoroutineScope.cancellableCounter() = withContext(Dispatchers.Default) {
        val someObject = SomeObject()
        while (someObject.keepRunning) {
            yield()
            someObject.value++
        }
    }
    

    该函数可以从另一个协程内部调用,也可以传递给asynclaunch,例如myScope.launch(::cancellableCounter)。返回的 Job 可以通过调用 cancel() 来取消。

    但正如 cmets 中所述,可能有更好的设计方法,具体取决于 SomeObject 应该如何使用。

    编辑:也许对于 ServerSocket 你需要做这样的事情。我没有测试过,所以不能完全确定。但我认为您不想在协程中直接调用accept(),因为它可能会阻塞很长时间并且不配合取消。所以我建议你仍然需要一个专用线程。 suspendCancellableCoroutine 可以将此桥接到挂起函数。

    suspend fun awaitSomeSocket(): Socket = suspendCancellableCoroutine { continuation ->
        val socket: ServerSocket = generateSocket()
        continuation.invokeOnCancellation { socket.close() }
        thread {
            runCatching {
                val result = socket.use(ServerSocket::accept)
                continuation.resume(result)
            }
        }
    }
    

    【讨论】:

    • 我回复了 cmets:好吧,想象一下 someObjectServerSocket。在run 方法中,我将调用serverSocket.accept(),这是一个阻塞调用。在等待连接到网络时,我可能会出于多种原因改变主意,在这种情况下,我需要致电serverSocket.close()。不幸的是,取消协程不会close() 连接。
    • serverSocket.use { serverSocket.accept() } - 是的,它会在取消时关闭套接字。
    • 是的,虽然你可能不想在协程中直接调用accept(),因为它是一个长阻塞函数。它会占用一个线程池的线程,并且不会与协程的取消合作。
    • 啊,我误解了@salyela。我认为问题是资源泄漏,但关键是我们需要调用close() 才能取消。
    猜你喜欢
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-16
    • 2013-12-17
    • 1970-01-01
    相关资源
    最近更新 更多