Home > Net >  Replace a Thread -- that has state variable -- with a Coroutine
Replace a Thread -- that has state variable -- with a Coroutine

Time:09-17

To restate the title, I'm wondering if there is a way to convert the MyThread class below to a Kotlin Coroutine.

If you look closely, you will notice that the MyThread class has a property variable called someObject that can be modified from inside the both the run and the cancel methods. In this case SomeObject is completely encapsulated inside MyThread and I want to keep it that way. Is there a way to convert MyThread to a coroutine or do I already have the most elegant version of the code?

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

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

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

CodePudding user response:

A reusable coroutine is a suspend function where the only parameter is CoroutineScope, so something roughly equivalent to what you have is:

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

The function can be called from inside another coroutine, or it can be passed to async or launch, such as myScope.launch(::cancellableCounter). The returned Job can be cancelled by calling cancel() on it.

But as mentioned in the comments, there may be a better way to design it depending on how SomeObject is supposed to be used.

Edit: Maybe for the ServerSocket you'd need to do something like this. I haven't tested it, so not totally sure. But I don't think you want to directly call accept() in a coroutine because it blocks for potentially a long time and does not cooperate with cancellation. So I'm suggesting you still need a dedicated thread. suspendCancellableCoroutine can bridge this to a suspend function.

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)
        }
    }
}

CodePudding user response:

I think you want a class that can start its own coroutine? That seems like the equivalent, something like:

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
    }
}

Typically you'd do job.cancel() instead, and check isActive in the while loop - I don't think it matters here, but it might be worth doing it "properly" (and it is technically different to someObject.keepGoing going false for some other reason). And if you're doing that, maybe TenFour04's suggestion is better, since the only reason you need a class/object is so you can put externally visible run and cancel functions in it. If the coroutine just runs anyway, and you call cancel on the Job it returns, it's all good!

  • Related