当前位置:网站首页>Kotlin collaboration -- context and exception handling

Kotlin collaboration -- context and exception handling

2022-06-13 06:26:00 m0_ forty-seven million nine hundred and fourteen thousand one

One . Collaboration context

1. The composition of the context of the collaboration

CoroutineContext Is a set of elements that define the behavior of a coroutine . It consists of the following .
Job: Control the lifecycle of the process
CoroutineDispatcher: Distribute tasks to the appropriate threads
CoroutineName: The name of the program , It's useful when debugging
CoroutineExceptionHandler: Handling uncapped exceptions

2. Combine elements in context

 @Test
 fun testCoroutineContext() = runBlocking {
               // Here, the scheduler and CoroutineName Operator addition 
        launch(Dispatchers.Default + CoroutineName("test")) {
            println("I'm working in thread ${Thread.currentThread().name}")
        }
    }

Print the results :

I'm working in thread DefaultDispatcher-worker-1 @test#2

Eat the name after the printout @test#2 Need to be in test Folder , Progressive unit testing will occur .

3. Inheritance of coroutine context

For the newly created collaboration , its CoroutineContext Will include a new J0b example , It will help us to control the lifecycle of the coprocessor . and The rest of the elements will The elements of CoroutineContext Inherits from the parent class of , The parent class may be another coroutine or the one that created it CoroutineScope.

 @Test 
fun testCoroutineContextExtend() = runBlocking {
        var scope = CoroutineScope(Job() + Dispatchers.IO + CoroutineName("testExtend"))
        val launch = scope.launch {
            //launch by scope It's the son of Xie Cheng , Will inherit scope The context of 
            println("${coroutineContext[Job]}    ${Thread.currentThread().name}")
            val async = async {
                //async by launch It's the son of Xie Cheng , Will inherit scope The context of 
                println("${coroutineContext[Job]}    ${Thread.currentThread().name}")
                "OK"
            }.await()
        }
        launch.join()

    }

Print the results :

"testExtend#2":StandaloneCoroutine{Active}@6318c9c9    DefaultDispatcher-worker-1 @testExtend#2
"testExtend#3":DeferredCoroutine{Active}@11fd5300    DefaultDispatcher-worker-3 @testExtend#3

The difference is because , Every Job The objects are all different .


The context of the coroutine = The default value is + inherited CoroutineContext+ Parameters
Some elements contain default values :Dispatchers.DefauIt By default CoroutineDispatcher, as well as “coroutine"
By default CoroutineName;
inherited CoroutineContext yes CoroutineScope Or his father's CoroutineContext;
Parameters passed into the collaboration builder take precedence over inherited context parameters , Therefore, the corresponding parameter value will be overridden .

 @Test 
fun testCoroutineContextExtend2() = runBlocking {
        val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
            println("Caught:$exception")
        }
        val coroutineScope = CoroutineScope(
            Job() + Dispatchers.Main + coroutineExceptionHandler
        )
        // If the collaboration built through the parent scope is not redefined, it will directly inherit the parameters of the parent class , If you define yourself, you will use your own                            
        // I've said before job The object of is different every time 
        coroutineScope.launch(Dispatchers.IO) {

        }
    }

Two . Exception handling of coroutine

1. Exception handling of root coroutine

There are two forms of coprocessor : Auto propagate exception (launch And actor), Expose exceptions to users (async And produce) When these builders are used to create a Root co process time ( This process is not a child of another process ), The former type of builder , An exception is thrown the first time it occurs , The latter relies on users to end up with abnormal consumption , For example, through await or receive.

// Root process abnormality 
fun testExceptionPropagation() = runBlocking<Unit> {
    // Auto propagate exception 
    //launch It was thrown abnormally 
    val launch = GlobalScope.launch {
        try {
            throw  IndexOutOfBoundsException()  // This line throws 
        } catch (e: Exception) {
            println("Caught IndexOutOfBoundsException")
        }

    }
    launch.join()
    // Propagate exceptions to users 
    //async It was thrown abnormally 

    val async = GlobalScope.async {
        throw ArrayIndexOutOfBoundsException()
    }
    try {
        //async This exception will not be triggered when it is not started catch To 
        async.await() // This line throws 
    } catch (e: Exception) {
        println("Caught ArrayIndexOutOfBoundsException")
    }
}

Print the results :

Caught IndexOutOfBoundsException
Caught ArrayIndexOutOfBoundsException

This is illustrated by the above code ,launch The abnormal normal of can catch To ,async The exception of must be called await When the method is used catch

2. Non root coroutine exception handling

    // Non root coroutine exception    Of async An exception will be thrown immediately 
    fun testExceptionPropagation2() = runBlocking<Unit> {
        val coroutineScope = CoroutineScope(Job())
        val launch = coroutineScope.launch {
            async {
                throw IndexOutOfBoundsException()
            }
        }
        launch.join()
    }

This code will report an error as soon as it runs , here async If an exception occurs as a child process, it will be uploaded to the , and launch If the exception cannot be handled, an error will be reported .

3. Abnormal propagation characteristics

When a coroutine fails due to an exception , He will propagate the exception and pass it to its parent , Next, the parent will perform the following operations :

1. Cancel his own rank

2. Cancel himself

3. Pass the exception to its parent

4.SupervisorJob

    @Test
    // Use SupervisorJob Problems in one subprocess will not affect other subprocesses 
    fun testSupervisorJob() = runBlocking<Unit> {
        val coroutineScope = CoroutineScope(SupervisorJob())
        val launch = coroutineScope.launch {
            throw IndexOutOfBoundsException()
        }

        val launch1 = coroutineScope.launch {
            delay(1000)
            println(" No abnormality ")
        }
        //coroutineScope.cancel()// After the parent scope is canceled , The child threads inside will also be canceled 
        joinAll(launch, launch1)
    }
}

SupervisorJob The result is that one of the processes is abnormal , Other subprocesses will continue to execute .

5.supervisorScope 

//supervisorScope When an exception occurs in the scope of ,supervisorScope It will all fail 
fun testSupervisorScope() = runBlocking<Unit> {

    supervisorScope {

        //throw IndexOutOfBoundsException()
        val launch = launch {
            try {
                print("child 1 finished.")
                delay(Long.MAX_VALUE)
            } finally {

                print("child 2 finished.")
            }
        }
        yield()
        println("asdsadsafdsag")
        throw ClassNotFoundException()
    }
}

6. Capture exception

1.CoroutineExceptionHandler 

Use CoroutineExceptionHandler Catch the exception of the cooperation process .
When the following conditions are met , The exception will be caught :
opportunity An exception is thrown by a coroutine that automatically throws an exception ( Use launch, instead of async when );
Location stay CoroutineScope Of CoroutineContext Or in a root coroutine (CoroutineScope perhaps supe
rvisorScope Direct subprocess of ) in .

//launch Will automatically throw an exception, so you can catch    Still in Scope In the process of building the root collaboration 
fun testCoroutineExceptionHandler() = runBlocking<Unit> {
    val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }
    val job = GlobalScope.launch(coroutineExceptionHandler) {
        throw AssertionError() // Can capture 
    }

    val async = GlobalScope.async(coroutineExceptionHandler) {
        throw  ArithmeticException();// You can't 
    }
    job.join()
    async.await()
}

2. Global exception handling

The global exception handler can get all unhandled exceptions of the collaboration , However, it does not capture exceptions ,
Although not It can prevent the program from crashing , The global exception handler still has a very large capacity in scenarios such as program debugging and exception reporting
use .
We need to be in classpath Create below META-lNF/services Catalog , And create one of them called kot|inx.
coroutines.CoroutineExceptionHandler The file of , The contents of the file are our global exception handlers
Full class name of .

class GlobalCoroutineExceptionHandler:CoroutineExceptionHandler {
    override val key= CoroutineExceptionHandler

    override fun handleException(context: CoroutineContext, exception: Throwable) {
       Log.d("ning","Unhandled Coroutine Exception: $exception")
    }
}

First create the exception capture class .

  Then create kot|inx.coroutines.CoroutineExceptionHandler file

com.example.kotlincoroutineexception.GlobalCoroutineExceptionHandler

It contains the full path of the exception capture class .

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
      
        button.apply {
            setOnClickListener {
                GlobalScope.launch {
                    "abc".substring(10)
                }
            }
        }
    }

Then you can catch it by reporting an error in the process .

Capture information :

Unhandled Coroutine Exception: java.lang.StringIndexOutOfBoundsException: length=3; index=10

3. Cancel and exception

1. Cancellation is closely related to exceptions , Used internally in the process CancellationException To cancel , This exception will be ignored .

2. When the subprocess is cancelled , It will not cancel its parent process .


fun testCancelAndException() = runBlocking<Unit> {
 launch {
     val child = launch {
         try {
            // Delay , Hang up 
             delay(Long.MAX_VALUE)
         } finally {
             println("Child is cancelled")
         }
     }
     yield()
     println("Cancelling child")
     child.cancelAndJoin()
     yield()
     println("Parent is not cancelled.")
 }
}

Print the results :

Cancelling child
Child is cancelled
Parent is not cancelled.

If a collaboration encounters CanceIIationException An exception , It will use this exception to cancel its parent coroutine . When all the child processes of the parent process are finished , Exceptions will be handled by the parent coroutine .

// If a collaboration encounters an exception other than cancellation , He will use the exception to cancel its parent 、 The parent process must close all the child processes to handle exceptions ( Throw out )

fun testCancelAndException2() = runBlocking<Unit> {
  val handler = CoroutineExceptionHandler() { _, e ->
      println("Caught $e")
  }
    val launch = GlobalScope.launch(handler) {
        launch {
            try {
                // Delay , Hang up 
                delay(Long.MAX_VALUE)
            } finally {
                withContext(NonCancellable) {
                    println("Children are cancelled, but exception is not handled until all")
                    delay(100)
                    println("The first child finished its non cancellable block ")
                }
            }
        }

        launch {
            delay(100)
            println("Second child h")
        }
    }
    launch.join()
}

Print the results :

Second child h
Children are cancelled, but exception is not handled until all
The first child finished its non cancellable block 
Caught java.lang.ArithmeticException

4. Catch exceptions of all sub processes

When multiple child processes of a process fail due to exceptions , Generally, the first exception is taken for processing . All other exceptions that occur after the first exception , Will be Bind to the first exception .

// Check the exceptions of all sub processes 
fun testExceptionAggregation() = runBlocking<Unit> {
    val handler = CoroutineExceptionHandler() { _, e ->
        println("Caught $e      ${e.suppressed.contentToString()}")
    }
    val launch = GlobalScope.launch(handler) {
        launch {
            try {
                delay(Long.MAX_VALUE)
            } finally {
                throw ArithmeticException();
            }
        }

        launch {
            try {
                delay(Long.MAX_VALUE)
            } finally {
                throw IndexOutOfBoundsException();
            }
        }

        launch {

              throw IOException();

        }
    }
    launch.join()
}

Print the results :

Caught java.io.IOException      [java.lang.ArithmeticException, java.lang.IndexOutOfBoundsException]

原网站

版权声明
本文为[m0_ forty-seven million nine hundred and fourteen thousand one ]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202270555276893.html