【问题标题】:How to convert Java blocking function into cancellable suspend function?如何将 Java 阻塞函数转换为可取消的挂起函数?
【发布时间】:2019-11-13 08:05:26
【问题描述】:

按照约定 (1),Kotlin suspend 函数应该是非阻塞的。通常我们有旧的 Java 代码,它依赖于 java 线程中断机制,我们不能(不想)修改(2):

public void doSomething(String arg) {
    for (int i = 0; i < 100_000; i++) {
        heavyCrunch(arg, i);
        if (Thread.interrupted()) {
            // We've been interrupted: no more crunching.
            return;
        }
    }
}

修改此代码以在协程中使用的最佳方法是什么?

版本 A:不可接受,因为它将在调用者线程上运行代码。所以会违反“挂起函数不阻塞调用者线程”的约定:

suspend fun doSomething(param: String) = delegate.performBlockingCode(param)

版本 B:更好,因为它会在后台线程中运行阻塞函数,因此它不会阻塞调用者线程(除非调用者偶然使用来自 Dispatchers.Default 的相同线程线程池)。但是协程作业取消不会中断performBlockingCode(),它依赖于线程中断。

suspend fun doSomething(param: String) = withContext(Dispatchers.Default) {
    delegate.performBlockingCode(param)
}

C 版:目前是我认为使其工作的唯一方法。这个想法是用Java机制将阻塞函数转换为非阻塞函数,然后使用suspendCancellableCoroutine(3)将异步方法转换为挂起函数:

private ExecutorService executor = Executors.newSingleThreadExecutor();

public Future doSomethingAsync(String arg) {
    return executor.submit(() -> {
        doSomething(arg);
    });
}

suspend fun doSomething(param: String) = suspendCancellableCoroutine<Any> { cont ->
    try {
        val future = delegate.doSomethingAsync(param)
    } catch (e: InterruptedException) {
        throw CancellationException()
    }
    cont.invokeOnCancellation { future.cancel(true) }
}

如下所述,上面的代码将无法正常工作,因为没有调用 continuation.resumeWith()

版本 D:使用 CompletableFuture:它提供了一种在可完成完成时注册回调的方法:thenAccept

private ExecutorService executor = Executors.newSingleThreadExecutor();

public CompletableFuture doSomethingAsync(String arg) {
    return CompletableFuture.runAsync(() -> doSomething(arg), executor);
}

suspend fun doSomething(param: String) = suspendCancellableCoroutine<Any> { cont ->
    try {
        val completableFuture = delegate.doSomethingAsync(param)
        completableFuture.thenAccept { cont.resumeWith(Result.success(it)) }
        cont.invokeOnCancellation { completableFuture.cancel(true) }
    } catch (e: InterruptedException) {
        throw CancellationException()
    }
}

你知道有什么更好的方法吗?

  1. https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html
  2. https://medium.com/@elizarov/blocking-threads-suspending-coroutines-d33e11bf4761
  3. https://medium.com/@elizarov/callbacks-and-kotlin-flows-2b53aa2525cf

【问题讨论】:

  • 你的第三种方法被打破了,它永远不会自行完成。你会发现完成是一项挑战,因为你从Future 得到的只是阻塞get()join()。你需要一个CompletableFuturesupplyAsync(fn, executor)
  • Kotlin 库支持这一点的方式是提供CompletableFuture.await()。唉,调用future.cancel(false) 是硬编码的。但我认为你可以简单地以不同的方式编写自己的扩展。在CompletableFuture 上编写扩展更容易与其他代码组合,可以让您不必每次都编写可挂起的包装器。请记住,中断带有协程的线程通常是一种危险的做法,可能会导致意外接收者接收到中断信号。
  • 我已经编辑了描述,添加了“版本 D”。这就是你实现它的方式吗? (我没有测试代码是否运行)。我也找到了这个答案:stackoverflow.com/a/58402108/2075875 这也应该是 Futures 的有效解决方案。 (我们针对的Android没有CompletableFuture)
  • 是的,这看起来是个不错的解决方案。在没有CompletableFuture 的情况下,我会同意。

标签: kotlin coroutine kotlin-coroutines


【解决方案1】:

你可以通过suspend fun kotlinx.coroutines.runInterruptible包装阻塞代码

它抑制了编译警告并且阻塞代码将在取消时抛出InterruptedException

val job = launch {
  runInterruptible {
    Thread.sleep(500)
  }
}

job.cancelAndJoin() // Cause will be 'java.lang.InterruptedException'

org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.2上测试

【讨论】:

    猜你喜欢
    • 2015-11-15
    • 2020-07-19
    • 2020-06-27
    • 1970-01-01
    • 1970-01-01
    • 2021-09-29
    • 2020-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多