Как запустить несколько Kotlin сопрограмм параллельно и дождаться их завершения, прежде чем продолжить - PullRequest
1 голос
/ 17 апреля 2020

Мне нужно запустить 2 сопрограммы параллельно и дождаться их завершения sh, прежде чем продолжить. Код ниже работает, но он использует GlobalScope, что не лучший способ сделать это.

Есть ли лучший способ?

fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
        GlobalScope.launch(Dispatchers.IO) {
            try {
                coroutineScope {
                    launch { getOne() }
                    launch { getTwo() }
                }
                onSuccess.invoke()
            } catch (e: Throwable) {
                onError.invoke()
            }
        }
    }

Ответы [ 4 ]

1 голос
/ 17 апреля 2020

Вы можете создать CoroutineScope внутри класса, над которым вы работаете, и создать сопрограммы, просто вызвав launch builder. Примерно так:

class MyClass: CoroutineScope by CoroutineScope(Dispatchers.IO) { // or [by MainScope()]

    fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
        launch {
            try {
                val jobA = launch { getOne() }
                val jobB = launch { getTwo() }
                joinAll(jobA, jobB)
                onSuccess.invoke()
            } catch (e: Throwable) {
                onError.invoke()
            }
        }
    }

    fun clear() { // Call this method properly
        this.cancel()
    }

}

Обязательно отмените все сопрограммы, работающие внутри области, правильно вызвав cancel.

Или, если вы работаете внутри ViewModel, просто используйте viewModelScope вместо.

1 голос
/ 17 апреля 2020

Я бы предложил реализовать getInfo как функцию приостановки, которая знает контекст, в котором она должна выполняться. Таким образом, не имеет значения, из какого контекста вы его вызываете (*).

Кроме того, я бы не использовал обратные вызовы для продолжения работы. Вы можете просто выбрать из того, что getInfo() возвращает, как действовать (**).

Это на самом деле самое большое в сопрограммах, вы можете превратить код на основе обратного вызова в код, который читается как последовательный код.

Так как вам не нужны результаты getOne() и getTwo(), использование launch является правильным способом. Возвращает Job. Вы можете приостановить сопрограмму, пока обе функции не будут завершены с joinAll(), который может быть вызван на Collection<Job>.

suspend fun getInfo() = withContext(Dispatchers.IO) {
    try {
        listOf(
            launch { getOne() },
            launch { getTwo() }
        ).joinAll()
        false
    } catch (e: Throwable) {
        true
    }
}

Вам не нужно использовать GlobalScope, просто создайте свою собственную (** *).

Я использовал Default в качестве контекста для запуска getInfo, но любой другой контекст тоже подойдет, так как getInfo будет работать на том, который должен.

val newScope = CoroutineScope(Dispatchers.Default).launch {
    val error = getInfo()
    if(error) {
        onSuccess()
    } else {
        onError()
    }
}
// "newScope" can be cancelled any time

* В случае, если я использовал Dispatcher.IO, чтобы представить, что две функции выполняют какую-то длительную работу ввода-вывода.

** Я использовал простой здесь логическое значение, но, конечно, вы можете вернуть что-то более значимое.

*** или подключитесь к некоторой области, заданной средой Sourrouding, учитывающей жизненный цикл

1 голос
/ 17 апреля 2020

Вы можете использовать любой объем, который вам нравится. Это не влияет на ваш вопрос. GlobalScope не рекомендуется, потому что он не инкапсулирует ваши задачи.

Какая область фактически использовать для запуска сопрограммы будет полностью зависеть от контекста того, что вы делаете, и от того, какую стратегию инкапсуляции вы используете.

Чтобы запустить несколько сопрограмм одновременно и дождаться их всех, используйте async и await(). Вы можете использовать awaitAll() в их списке, если вам просто нужно, чтобы все они закончили sh перед продолжением.

Кажется странным, что вы используете диспетчер ввода-вывода для запуска ваших обратных вызовов, но я ' Я оставлю это, потому что я слеп к контексту вашего кода.

fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
        GlobalScope.launch(Dispatchers.IO) {
            try {
                coroutineScope {
                    listOf(
                        async { getOne() }
                        async { getTwo() }
                    }.awaitAll()
                }
                onSuccess.invoke()
            } catch (e: Throwable) {
                onError.invoke()
            }
        }
    }
0 голосов
/ 22 апреля 2020

На самом деле есть даже лучший способ, основанный на принятом ответе:

suspend fun getInfo() = withContext(Dispatchers.IO) {
try {
    coroutineScope {
        launch { getOne() }
        launch { getTwo() }
    }
    false
} catch (e: Throwable) {
    true
}

}

...