如何取消协程中的阻塞代码

Vic*_*kiy 2 android asynchronous kotlin kotlin-coroutines

我有以下代码结构:

 @Throws(InterruptedException::class)
 fun method() {
     // do some blocking operations like Thread.sleep(...)
 }
 var job = launch {
     method()
 }
 job.cancelAndJoin()
Run Code Online (Sandbox Code Playgroud)

method由外部库提供,我无法控制其行为。执行可能需要很多时间,因此在某些情况下应该通过超时取消。

我可以使用withTimeoutkotlin协程库提供的功能,但是由于协程设计,它无法取消带有阻塞的代码。它有一些解决方法吗?

Vic*_*kiy 5

主要思想是将协程外上下文线程池与可以在旧样式中中断的 JVM 线程一起使用,并从协程执行中订阅取消事件。当事件被 捕获时invokeOnCancellation,我们可以中断当前线程。

实施:

val externalThreadPool = Executors.newCachedThreadPool()
suspend fun <T> withTimeoutOrInterrupt(timeMillis: Long, block: () -> T) {
    withTimeout(timeMillis) {
        suspendCancellableCoroutine<Unit> { cont ->
            val future = externalThreadPool.submit {
                try {
                    block()
                    cont.resumeWith(Result.success(Unit))
                } catch (e: InterruptedException) {
                    cont.resumeWithException(CancellationException())
                } catch (e: Throwable) {
                    cont.resumeWithException(e);
                }
            }
            cont.invokeOnCancellation {
                future.cancel(true)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它提供了与通常类似的行为withTimeout,但它还支持运行带有阻塞的代码。

注意:只有当您知道内部代码使用阻塞并且可以正确处理抛出的InterruptedException. 在大多数情况下,该withTimeout功能是首选。

更新:从协程版本 1.3.7 开始,有一个新函数runInterruptible,它提供了相同的行为。所以这段代码可以简化:

suspend fun <T> withTimeoutOrInterrupt(timeMillis: Long, block: () -> T) {
    withTimeout(timeMillis) {
        runInterruptible(Dispatchers.IO) {
            block()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)