как сделать приостановить вызовы "ждать" блокирующий поток внутри, в Kotlin? - PullRequest
2 голосов
/ 03 октября 2019

Позвольте мне объяснить это лучше ...

У меня приостановлена ​​функция

suspend fun foo(){
   if(startFlag){
     myMethod()
    }
}

, когда я в первый раз вызываю myMethod(), я проверю значение startFlag и если оно ложно, вызову myMethod.

Я не могу использовать init {} для этого, должно быть, когда я звоню foo() в первый раз (из-за более сложной логики).

Моя проблема:

Вв первый раз, когда я позвоню foo(), позвоню myMethod() (допустим, говорит, что решение занимает много времени)

Затем будет сделан другой вызов foo(), я хочу обнаружить, что myMethod() ужеработает в потоке, и я должен ждать его.

Но так как fun foo() само по себе приостановлено, не уверен, смогу ли я это сделать.

Есть идеи?

Ответы [ 3 ]

4 голосов
/ 03 октября 2019

Это интересная проблема, и я хочу поделиться подходом к ее решению, надеясь, что это поможет.

Основное решение

Вы можете восстановить вычисление myMethod(), используя Deferred. В основном код может выглядеть следующим образом:

    val myMethodDeferred = GlobalScope.async {
        myMethod()
    }

    suspend fun foo(){
        myMethodDeferred.await()
    }

Функция async возвращает объект Deferred, который содержит вычисление myMethod. Когда вы вызываете myMethodDeferred.await(), он ожидает окончания вычисления и возвращает результат myMethod (для использования при необходимости).

Если вы хотите, чтобы вычисление выполнялось только при вызове foo()Вы можете добавить параметр к вызову async следующим образом: async(start = CoroutineStart.LAZY){ .... Это приведет к ленивому запуску вычисления при первом вызове .await().

(GlobalScope можно заменить любым CoroutineScope или областью, которая передается в качестве параметра конструктора классу. Если myMethod() не является функцией приостановки, а функцией блокировки, как вы описали, вы можете использовать соответствующий CoroutineScope, например, CoroutineScope(Dispatchers.IO), если он выполняет вычисления ввода-вывода.)

Обработка ошибок

Недостатком этого решения является случай, когда myMethod() завершился неудачей с исключением. Если произошел сбой myMethod(), сбой будет сохранен в отложенном режиме, и каждый вызов на foo() тоже будет неудачным, вместо того, чтобы пытаться снова запустить myMethod().

Для обработки сбоев myMethod(),Я предлагаю объявить класс с именем Retryable, который будет содержать вычисления, которые можно повторить, аналогично тому, как Deferred проводит вычисления. Код:

class Retryable<T>(private val computation: suspend CoroutineScope.() -> T) {

    private var deferred: Deferred<T>? = null

    suspend fun await(): T {
        val oldDeferred = deferred
        val needsRetry = oldDeferred == null || oldDeferred.isCompleted && oldDeferred.getCompletionExceptionOrNull() != null
        if (needsRetry) {
            deferred = GlobalScope.async(block = computation)
        }
        return deferred!!.await()
    }

}

Использование должно выглядеть следующим образом:

    val myMethodRetryable = Retryable { myMethod() }

    suspend fun foo(){
        myMethodRetryable.await()
    }

Теперь каждый вызов foo() будет ожидать завершения вычисления myMethod(), ноесли он уже завершился с ошибкой, он перезапустит его и будет ждать новых вычислений.

(Здесь CoroutineScope должен быть передан в качестве параметра Retryable, но я не хочуусложнить пример кода.)


Альтернативное решение: Используйте асинхронную фабричную функцию

Поскольку myMethod() следует вызывать только один раз, если вы не можете вызвать ее на init {}, вы можетевместо этого вызовите ее для заводской функции.

Пример кода:

class MyClass(private val myMethodResult: String) {
  companion object {
    suspend fun create(): MyClass {
        val myMethodResult = myMethod()
        return MyClass(myMethodResult)
     }
     private suspend fun myMethod(): String {
        ...
     }
  }
  ...
}

Полезно, если вы можете инициализировать MyClass один раз из функции приостановки перед ее использованием.

(Если myMethod() не является функцией приостановки, а блокирующей функцией, как вы описали, вы можете захотеть обернуть ее соответствующим контекстом, например withContext(Dispatchers.IO) { myMethod() }, если она выполняет вычисления ввода / вывода.)

1 голос
/ 03 октября 2019

Вы можете просто использовать Semaphore.


...

suspend fun foo(semaphore: Semaphore) = semaphore.withPermit {

    if (startFlag) {
        myMethod()
    }
}
...

, а затем создать и передать в качестве аргумента Semaphore с 1 разрешением.

val myJobSemaphore = Semaphore(1)
0 голосов
/ 03 октября 2019

Если я вас правильно понял, это можно сделать с помощью Mutex, например:

var startFlag = true
val mutex = Mutex()

suspend fun foo() = mutex.withLock {
    if (startFlag) {
        myMethod()
        startFlag = false
    }
}

здесь есть ссылка на детская площадка

...