The invokeOnCompletion
will always be invoked, but it has a number of different characteristics vs. its try
counterpart.
try -catch -finally |
invokeOnCompletion |
---|---|
|
|
GlobalScope.launch(Dispatchers.IO) {
try {
// todo
} finally {
println("finally: ${Thread.currentThread().name}") // executes in current Coroutine context
}
}.apply {
invokeOnCompletion {
println("invokeOnCompletion: ${Thread.currentThread().name}") // executes on arbitrary thread
}
}
Output:
finally: DefaultDispatcher-worker-1
invokeOnCompletion: main
tl;dr You are essentially forfeiting some Coroutines functionality by using a completion handler that executes outside the Coroutine scope.
This isn't to say that invokeOnCompletion
shouldn't be used (it has its purpose) but it's a pretty narrow subset of cases, IMHO.
If you always want something to be invoked even when the Coroutine is cancelled (and never started) then it would be good to use invokeOnCompletion
:
val job = GlobalScope.launch(Dispatchers.IO, start = CoroutineStart.LAZY) {
try {
// todo
} finally {
println("finally: ${Thread.currentThread().name}")
}
}.apply {
invokeOnCompletion {
println("invokeOnCompletion: ${Thread.currentThread().name}")
}
}
job.cancel()
Output:
invokeOnCompletion: main
1 To demonstrate item 1 in the table above:
val handler = CoroutineExceptionHandler { _, cause ->
println("handler: $cause")
}
GlobalScope.launch(Dispatchers.IO + handler) {
try {
// todo
} finally {
error("launch")
}
}.apply {
invokeOnCompletion {
error("invokeOnCompletion")
}
}
Output:
handler: java.lang.IllegalStateException: launch
handler: kotlinx.coroutines.CompletionHandlerException: Exception in completion handler InvokeOnCompletion[InvokeOnCompletion@4f5e20] for StandaloneCoroutine{Cancelled}@e6fff40