Обработка исключений сопрограммы Kotlin - как абстрагироваться от try-catch - PullRequest
1 голос
/ 07 ноября 2019

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

Если яокружить мой вызов async.await () блоком try-catch, он работает как задумано. Тем не менее, если я пытаюсь абстрагировать этот try-catch в функцию расширения, мое приложение вылетает.

Что мне здесь не хватает?

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class Main2Activity : AppCompatActivity() {

    private val job: Job = Job()
    private val scope = CoroutineScope(Dispatchers.Default + job)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        runCode()
    }

    private suspend fun asyncCallThrowsException(): Deferred<Boolean> =
        withContext(Dispatchers.IO) {
            Thread.sleep(3000)// simulates a blocking request/response (not on the Main thread, though)
            throw(Exception())
        }

    suspend fun <T> Deferred<T>.awaitAndCatch() {
        try {
            this.await()
        } catch (e: Exception) {
            println("exception caught inside awaitAndCatch")
        }
    }

    private fun runCode() {
        scope.launch {

            //This block catches the exception.
            try {
                val resultDeferred = asyncCallThrowsException()
                resultDeferred.await()
            } catch (e: Exception) {
                println("exception caught inside try-catch")
            }

            //This line does not, and crashes my app.
            asyncCallThrowsException().awaitAndCatch()
        }
    }
}

Редактировать: Iна самом деле забыл обернуть вызов внутри блока async. Теперь даже не работает явный блок try-catch ...

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class Main4Activity : AppCompatActivity() {

    private val job: Job = Job()
    private val scope = CoroutineScope(Dispatchers.Default + job)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runCode()
    }

    private suspend fun callThrowsException(): String =
        withContext(Dispatchers.IO) {
            Thread.sleep(3000)// simulates a blocking request/response (not on the Main thread, though)
            throw(Exception())
            "my result"
        }

    suspend fun <T> Deferred<T>.awaitAndCatch(): T? {
        try {
            return this.await()
        } catch (e: Exception) {
            println("exception caught inside awaitAndCatch")
        }
        return null
    }

    private fun runCode() {
        scope.launch {

            val resultDeferred: Deferred<String> = async { callThrowsException() }
            var result: String?

//            This doesn't catch the throwable, and my app crashes - but the message gets printed to the console.
            try {
                result = resultDeferred.await()
            } catch (e: Exception) {
                println("exception caught inside try-catch")
            }

//            This doesn't catch the throwable, and my app crashes - but the message gets printed to the console.
            result = resultDeferred.awaitAndCatch()
        }
    }
}

Ответы [ 2 ]

2 голосов
/ 08 ноября 2019

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

Даже если ваш код может перехватить исключение и напечатать сообщение, родительская работа будет прервана. КАК МОЖНО СКОРЕЕ.

Вместо того, чтобы делать это так: val: Job = Job(), попробуйте val: Job = SupervisorJob()

Задание супервизора не отменяется, когда его дочерние элементы терпят неудачу, поэтому это не приведет к сбою приложения.

Или, если вы хотите запустить асинхронное задание без этой проблемы, см .: Безопасная асинхронность в заданной области действия

1 голос
/ 08 ноября 2019

Чтобы найти правильное решение, нужно решить проблему совместимости его с принципами структурированного параллелизма.

Какова ваша мотивация использовать async? Что вы планируете делать между запуском async и его ожиданием?

Если и запуск async, и вызов await являются частью одной единицы работы,и успех вызова async является предпосылкой общего успеха, затем оберните всю единицу работы в coroutineScope.

Если вы хотите запустить эту задачу в фоновом режиме и ожидать ее отобратный вызов Android, который вызывается позже, тогда его нельзя заключить в одну единицу работы. Вы должны прикрепить асинхронную задачу к верхнему уровню CoroutineScope, который должен иметь SupervisorJob.

Правильный способ сделать это показан в документации CoroutineScope:

class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    override fun onDestroy() {
        cancel() // cancel is extension on CoroutineScope
    }

    ...
}

В стандартную библиотеку Kotlin добавлен делегат MainScope() для удобства, чтобы вы не ошиблись.

...