Функции Kotlin suspend
должны быть неблокирующими по соглашению (1). Часто у нас есть старый код 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) . Но отмена задания сопрограммы не прерывает функцию executeBlockingCode(), которая зависит от прерывания потока.
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) }
}
Как указано ниже, приведенный выше код не будет работать должным образом, поскольку не вызывается continue.resumeWith()
Версия D: использует CompletableFuture:, который предоставляет способ регистрации обратного вызова при завершении completable: 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()
}
}
Знаете ли вы лучший способ для этого?
- https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html
- https://medium.com/@elizarov/blocking-threads-suspending-coroutines-d33e11bf4761
- https://medium.com/@elizarov/callbacks-and-kotlin-flows-2b53aa2525cf
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
Похожие вопросы
Связанные вопросы
Новые вопросы
kotlin
Kotlin — это язык программирования высокого уровня, разработанный JetBrains. Используйте этот тег, если у вас возникли проблемы с языком Kotlin и стандартной библиотекой. Этот тег часто используется вместе с дополнительными тегами для различных целей (JVM, JavaScript, нативные и т. д.) и библиотек/фреймворков (Android, Spring и т. д.), используемых разработчиками Kotlin, если вопрос относится именно к этим темам.
Future
, — это блокировкаget()
илиjoin()
. Вам нуженCompletableFuture
:supplyAsync(fn, executor)
.CompletableFuture.await()
. Увы, вызовfuture.cancel(false)
жестко запрограммирован. Но я думаю, что вы могли бы просто написать собственное расширение по-другому. Написание расширения наCompletableFuture
более совместимо с другим кодом, что может избавить вас от необходимости каждый раз писать приостанавливаемую оболочку. Имейте в виду, что прерывание потоков, несущих сопрограмму, в целом является опасной практикой и может привести к тому, что сигнал прерывания будет получен непреднамеренным получателем.CompletableFuture
я бы выбрал это.