android kotlin Coroutines
launch, async를 사용할때 새로운 concurrent가 만들어지고 동시 작업이 수행된다. withContext()는 새로 concurrent가 만들어지지 않고 thread만 바꾼다.
nested 된 형태의 coroutines 인경우는 일반 coroutines의 흐름과 좀 다르다. 일반적으로nested된 coroutine의 경우 concurrent하게 진행되는데 nested된 coroutine이 완료될때까지 멈췄다 진행하려는 경우 coroutineScope{}를 이용한 Structured concurrency 를 이용한다.
coroutineScope 나 supervisorScope를 scope builder라고 한다. structured coroutine을 만들때 사용하며 { } 안의 코드가 완료될때 까지 nesting coroutine은 기다린다.
SupervisorJob의 경우 한 coroutine에서 발생한 exception은 propangate되지 않는다. 다른 coroutines은 그대로 작동한다.그래도 exception은 발생하므로 try catch로 처리 가능.
(한 scope에 하나의 coroutines만 있으며 SupervisorJob를 사용한 경우 exception이 발생해도 exception이 raise되지만 프로그램이 멈추진 않는다. )
Job의 경우 한 coroutine에서 발생한 exception은 propangate되며 exception처리를 하지않으면 멈춰버리게 된다. 처리하면 propagate는 멈추고 잘 작동된다.
supervisorjob은 CoroutineScope(SupervisorJob()) 이런식으로 하거나 supervisorScope { }를 통해서만 적용할수 있다.
CoroutineScope 내에서 async가 direct child 인 경우에만 그 안에서 발생하는 exception은 바로 throw되지 않고 await() 호출될때까지 유보된다. direct child가 아닌 경우 바로 throw
exception handler는 아래 조건이 맞을 때만 적용가능하다.
When : The exception is thrown by a coroutine that automatically throws exceptions (works with launch, not with async).
Where : If it’s in the CoroutineContext of a CoroutineScope or a root coroutine (direct child of CoroutineScope or a supervisorScope).
launch는 Job을 리턴한다. async는 deferred를 리턴하며 await()을 호출해서 coroutine내 작업을 모두 마무리하게 할수 있으며 Job도 포함하고 있어서 coroutine을 취소 할수도 있다.
concurrent is started when we use 'launch' or 'async'
withContext() is not start new concurrent. it just change thread
launch, async를 사용할때 새로운 concurrent가 만들어지고 동시 작업이 수행된다. withContext()는 새로 concurrent가 만들어지지 않고 thread만 바꾼다. 옮겨진 thread에서의 작업이 완수 될때 까지 기다렸다 진행된다. 예제 코드 아래 참조
import kotlinx.coroutines.*
fun main(args: Array<String>){
println("main functions started")
val job1 = SupervisorJob()
// val scope = CoroutineScope(Dispatchers.Default + job1)
val scope = CoroutineScope(job1)
runBlocking {
// launch되고 밑에 코드들 실행
scope.launch {
databasejob1() //databasejob1이 마무리되고 다음실행,withContext()를 사용하더라도 기본적으로 parent scope를 따른다. 바꾸고 싶은경우 launch, async를 이요한다
databasejob2()
}
scope.launch{
}
// 만약 이부분이 1000이었다고 한다면 databasejob1, databasejob2가 끝나기전에 끝나버림
delay(50000L)
}
}
private suspend fun databasejob1(){
withContext(Dispatchers.IO){
delay(2000L)
println("databasejob1===================")
}
}
private suspend fun databasejob2(){
withContext(Dispatchers.IO){
delay(2000L)
println("databasejob2===================")
}
}
CoroutineScope()와 coroutineScope{} 의 차이
nested 된 형태의 coroutines 인경우는 일반 coroutines의 흐름과 좀 다르다. 일반적으로nested된 coroutine의 경우 concurrent하게 진행되는데 nested된 coroutine이 완료될때까지 멈췄다 진행하려는 경우 coroutineScope{}를 이용한 Structured concurrency 를 이용한다.
https://stackoverflow.com/questions/59368838/difference-between-coroutinescope-and-coroutinescope-in-kotlin
CoroutineScope() is nothing but a factory of CoroutineScope objects, and a CoroutineScope object is nothing but a holder of a CoroutineContext. It has no active role in coroutines, but it's an important part of the infrastructure that makes it easy to do structured concurrency properly. This comes from the fact that all coroutine builders like launch or async are extension functions on CoroutineScope and inherit its context.
You will rarely, if ever, have the need to call CoroutineScope() because usually you either pick up an existing coroutine scope or have one created for you by other convenience functions (like MainScope on Android) or Kotlin internals.
coroutineScope(), on the other hand, is one of the coroutine builders, which means it executes the block you pass it inside a sub-coroutine. It is basically an alias for withContext(this.coroutineContext) 이것은 부모(nesting) 코루틴 context를 그대로 사용한다는 의미이다. and you should primarily use it when you want to launch one or more background coroutines while you continue some work in the foreground, and then join on the background coroutines when completing the block.
==========================================================
Best difference between CoroutineScope (Capital C version) vs coroutineScope (Smaller c version), I could figure out and which was easily understandable was correlating them with Unstructured vs Structured concurrency
Let me share an example :
class MainActivity extends Activity { private Button btn; public void onCreate(Bundle b) { setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.start_btn); btn.setOnClickListener( () -> { // Starting a coroutine in Main scope, to download user data, and will print it CoroutineScope(Dispatchers.Main).launch { int result = downloadUserData() Toast.makeText(applicationContext, "Result : " + result, Toast.LENGTH_LONG).show() }); } private suspend int downloadUserData() { int result = 0; // Here, we use CoroutineScope (Capital C version) which will start a new scope and // launch coroutine in new scope Dispatchers.IO, Not In Parent Scope which is Dispatchers.Main // Thus, this function would directly return without waiting for loop completion and will return 0. 이렇게 하면 nesting하는 parent scope를 그대로 이용하게되고 작업이 순차적으로 진행된다 CoroutineScope(Dispatchers.IO).launch { for (int i = 0; i < 2000; i++) { kotlinx.coroutines.delay(400); result++; } } return result; } }
This is an example of Unstructured Concurrency where it is not guaranteed that child coroutine would complete before returning. Thus, caller/parent coroutine would get wrong value returned by child coroutine. Even, when child coroutine has returned already, child coroutine may be running (in Active state) in the background which may lead to Memory Leaks in certain cases.
When we need to communicate between multiple coroutines, we need to make sure Structured Concurrency (Recommended)
This can be done by re-using parent/caller coroutine scope inside child/callee coroutine. This can be achieved by coroutineScope {} (Smaller c) version inside child/callee coroutine.
private suspend int downloadUserData() { int result = 0; // By using coroutineScope (Smaller c version) below, we ensure that this coroutine would execute in the // parent/caller coroutine's scope, so it would make sure that the for loop would complete // before returning from this suspended function. This will return 20000 properly coroutineScope { for (int i = 0; i < 20000; i++) { kotlinx.coroutines.delay(400); result++; } } return result; }
한 scope에 여러개의 coroutines을 사용하는 경우
SupervisorJob의 경우 한 coroutine에서 발생한 exception은 propangate되지 않는다. 다른 coroutines은 그대로 작동한다.그래도 exception은 발생하므로 try catch로 처리 가능.
(한 scope에 하나의 coroutines만 있으며 SupervisorJob를 사용한 경우 exception이 발생해도 exception이 raise되지만 프로그램이 멈추진 않는다. )
Job의 경우 한 coroutine에서 발생한 exception은 propangate되며 exception처리를 하지않으면 멈춰버리게 된다. 처리하면 propagate는 멈추고 잘 작동된다.
https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c
A coroutine suddenly failed! What now? 😱
When a coroutine fails with an exception, it will propagate said exception up to its parent! Then, the parent will 1) cancel the rest of its children, 2) cancel itself and 3) propagate the exception up to its parent. (exception발생시 기본 작동)
-coroutine에서 exception발생하면 기본적으로 children이 있다면 취소되고 자신도 취소된다. 그리고 parent로 propagate된다.
The exception will reach the root of the hierarchy and all the coroutines that the CoroutineScope started will get cancelled too.
While propagating an exception can make sense in some cases, there are other cases when that’s undesirable. Imagine a UI-related CoroutineScope that processes user interactions. If a child coroutine throws an exception, the UI scope will be cancelled and the whole UI component will become unresponsive as a cancelled scope cannot start more coroutines.
What if you don’t want that behavior? Alternatively, you can use a different implementation of Job , namely SupervisorJob , in the CoroutineContext of the CoroutineScope that creates these coroutines.
SupervisorJob to the rescue
With a SupervisorJob, the failure of a child doesn’t affect other children. A SupervisorJob won’t cancel itself or the rest of its children. Moreover, SupervisorJob won’t propagate the exception either, and will let the child coroutine handle it. (그러나 처리 하지 않으면 exception은 위로 올라가진 않으나 throw되고 terminal 에 exception 메시지가 뜨게 된다)
You can create a CoroutineScope like this
val uiScope = CoroutineScope(SupervisorJob())
to not propagate cancellation when a coroutine fails as this image depicts:
If the exception is not handled and the CoroutineContext doesn’t have a CoroutineExceptionHandler (as we’ll see later), it will reach the default thread’s ExceptionHandler. it will make your app crash regardless of the Dispatcher this happens on.
💥 Uncaught exceptions will always be thrown regardless of the kind of Job you use
coroutineScope 나 supervisorScope를 scope builder라고 한다. structured coroutine을 만들때 사용하며 { } 안의 코드가 완료될때 까지 nesting coroutine은 기다린다.
The same behavior applies to the scope builders coroutineScope and supervisorScope. These will create a sub-scope (with a Job or a SupervisorJob accordingly as a parent) with which you can logically group coroutines (e.g. if you want to do parallel computations or you want them to be or not be affected by each other).
Warning: A SupervisorJob only works as described when it’s part of a scope: either created using supervisorScope or CoroutineScope(SupervisorJob()).
CoroutineScope(SupervisorJob()) 이런식으로 하거나 supervisorScope { }를 통해서만 SupervisorJob 기능을 적용할수 있다는 이야기. 이렇게 해야지만 하나의 coroutine에서 exception이 발생해도 다른 sibling coroutines은 원래대로 돌아가고 parent 도 멈추지 않는다.
When should you use a Job or a SupervisorJob? Use a SupervisorJob or supervisorScope when you don’t want a failure to cancel the parent and siblings.
// Scope handling coroutines for a particular layer of my app
val scope = CoroutineScope(SupervisorJob())
scope.launch {
// Child 1
}scope.launch {
// Child 2
}
In this case, if child#1 fails, neither scope nor child#2 will be cancelled.
// Scope handling coroutines for a particular layer of my app
val scope = CoroutineScope(Job())
scope.launch {
supervisorScope {
launch {
// Child 1
}
launch {
// Child 2
}
}
}
In this case, as supervisorScope creates a sub-scope with a SupervisorJob, if child#1 fails, child#2 will not be cancelled. If instead you use a coroutineScope in the implementation, the failure will get propagated and will end up cancelling scope too.
Watch out quiz! Who’s my parent? 🎯
Given the following snippet of code, can you identify what kind of Job child#1 has as a parent?
위에서 말했듯이 CoroutineScope(SupervisorJob()) 이런식으로 하거나 supervisorScope { }를 통해서만 적용할수 있다.
val scope = CoroutineScope(Job())
scope.launch(SupervisorJob()) {
// new coroutine -> can suspend
launch {
// Child 1
}
launch {
// Child 2
}
}
child#1’s parentJob is of type Job! Hope you got it right! Even though at first impression, you might’ve thought that it can be a SupervisorJob, it is not because a new coroutine always gets assigned a new Job() which in this case overrides the SupervisorJob. SupervisorJob is the parent of the coroutine created with scope.launch; so literally, SupervisorJob does nothing in that code!
Therefore, if either child#1 or child#2 fails, the failure will reach scope and all work started by that scope will be cancelled.
Remember that a SupervisorJob only works as described when it’s part of a scope: either created using supervisorScope or CoroutineScope(SupervisorJob()). Passing a SupervisorJob as a parameter of a coroutine builder will not have the desired effect you would’ve thought for cancellation.
Regarding exceptions, if any child throws an exception, that SupervisorJob won’t propagate the exception up in the hierarchy and will let its coroutine handle it.
If you’re curious about how Job works under the hood, check out the implementation of the functions childCancelled and notifyCancelling in the JobSupport.kt file.
In the SupervisorJob implementation, the childCancelled method just returns false, meaning that it doesn’t propagate cancellation but it doesn’t handle the exception either.
Dealing with Exceptions 👩🚒
Coroutines use the regular Kotlin syntax for handling exceptions: try/catch or built-in helper functions like runCatching (which uses try/catch internally).
We said before that uncaught exceptions will always be thrown. However, different coroutines builders treat exceptions in different ways.
With launch, exceptions will be thrown as soon as they happen. Therefore, you can wrap the code that can throw exceptions inside a try/catch, like in this example:
scope.launch {
try {
codeThatCanThrowExceptions()
} catch(e: Exception) {
// Handle exception
}
}
With launch, exceptions will be thrown as soon as they happen
When async is used as a root coroutine (coroutines that are a direct child of a CoroutineScope instance or supervisorScope), exceptions are not thrown automatically, instead, they’re thrown when you call .await().
CoroutineScope 내에서 async가 direct child 인 경우에 그 안에서 발생하는 exception은 바로 throw되지 않고 await() 호출될때까지 유보된다.
To handle exceptions thrown in async whenever it’s a root coroutine, you can wrap the .await() call inside a try/catch:
supervisorScope {
val deferred = async {
codeThatCanThrowExceptions()
} try {
deferred.await()
} catch(e: Exception) {
// Handle exception thrown in async
}
}
In this case, notice that calling async will never throw the exception, that’s why it’s not necessary to wrap it as well. await will throw the exception that happened inside the async coroutine.
When async is used as a root coroutine, exceptions are thrown when you call .await
Also, notice that we’re using a supervisorScope to call async and await. As we said before, a SupervisorJob lets the coroutine handle the exception; as opposed to Job that will automatically propagate it up in the hierarchy so the catch block won’t be called:
coroutineScope {
try {
val deferred = async {
codeThatCanThrowExceptions()
}
deferred.await()
} catch(e: Exception) {
// Exception thrown in async WILL NOT be caught here
// but propagated up to the scope
}
}
Furthermore, exceptions that happen in coroutines created by other coroutines will always be propagated regardless of the coroutine builder. For example:
val scope = CoroutineScope(Job())
scope.launch {
async {
// If async throws, launch throws without calling .await()
}
}
위의 코드는 CoroutineScope 내에서 async가 direct child 가 아닌 경우에 해당 바로 exception이 throw된다.
In this case, if async throws an exception, it will get thrown as soon as it happens because the coroutine that is the direct child of the scope is launch. The reason is that async (with a Job in its CoroutineContext) will automatically propagate the exception up to its parent (launch) that will throw the exception.
⚠️ Exceptions thrown in a coroutineScope builder or in coroutines created by other coroutines won’t be caught in a try/catch!
In the SupervisorJob section, we mention the existence of CoroutineExceptionHandler. Let’s dive into it!
CoroutineExceptionHandler
The CoroutineExceptionHandler is an optional element of a CoroutineContext allowing you to handle uncaught exceptions.
Here’s how you can define a CoroutineExceptionHandler, whenever an exception is caught, you have information about the CoroutineContext where the exception happened and the exception itself:
val handler = CoroutineExceptionHandler {
context, exception -> println("Caught $exception")
}
Exceptions will be caught if these requirements are met:
When : The exception is thrown by a coroutine that automatically throws exceptions (works with launch, not with async).
Where : If it’s in the CoroutineContext of a CoroutineScope or a root coroutine (direct child of CoroutineScope or a supervisorScope).
Let’s see some examples using the CoroutineExceptionHandler defined above. In the following example, the exception will be caught by the handler:
val scope = CoroutineScope(Job())
scope.launch(handler) {
launch {
throw Exception("Failed coroutine")
}
}
In this other case in which the handler is installed in a inner coroutine, it won’t be caught:
val scope = CoroutineScope(Job())
scope.launch {
launch(handler) {
throw Exception("Failed coroutine")
}
}
The exception isn’t caught because the handler is not installed in the right CoroutineContext. The inner launch will propagate the exception up to the parent as soon as it happens, since the parent doesn’t know anything about the handler, the exception will be thrown.
Dealing with exceptions gracefully in your application is important to have a good user experience, even when things don’t go as expected.
Remember to use SupervisorJob when you want to avoid propagating cancellation when an exception happens, and Job otherwise.
Uncaught exceptions will be propagated, catch them to provide a great UX!
android kotlin coroutines official docs
https://developer.android.com/kotlin/coroutines
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun makeLoginRequest(username: String, token: String) {
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
val result = try {
loginRepository.makeLoginRequest(jsonBody)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
withContext(Dispatchers.IO) 는 repository에서 하는 것을 추천한다.
makeLoginRequest is also marked with the suspend keyword. This keyword is Kotlin's way to enforce a function to be called from within a coroutine.
기본적으로 any coroutines launched from viewModelScope run in the main thread.
android kotlin coroutines official docs
https://developer.android.com/kotlin/coroutines-adv
https://kotlinlang.org/docs/reference/coroutines/composing-suspending-functions.html
async에 대한 설명
async lazy await structured
android Coroutines official docs
https://developer.android.com/topic/libraries/architecture/coroutines
livedata with coroutines 1분30초 매우 간결 안드로이드 운영체제 관리자가 설명https://youtu.be/GUvi1LS_8Kw
위 방법을 좀더 간결하게 한것이 아래아래 그림이다.
18분분량 안드로이드 공식 세미나 설명이 부족하나 개념만 대충 잡을수 있음https://www.youtube.com/watch?v=B8ppnjGPAGE&t=795s
emit() 과 emitSource()의 차이점
https://stackoverflow.com/a/58950866/3151712
https://codelabs.developers.google.com/codelabs/kotlin-android-training-live-data-transformations/index.html?index=..%2F..android-kotlin-fundamentals#7
livedata를 일반 data를 wrapper로 감싼 형태 가정하고 그안의 내용만을 수정한 livedata를 얻고자 하는 경우 Transformations를 이용해서 내용을 변경할수 있다.
android 와 Coroutines를 같이 사용하는 경우의 유의점 (80% 이해)
https://medium.com/@FreddieWang/some-tips-of-kotlin-coroutines-for-android-developers-f0988fbeb246
Tip1: Never use GlobalScope to launch the coroutines
According to the official document,
Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Application code usually should use application-defined CoroutineScope, using async or launch on the instance of GlobalScope is highly discouraged.
Instead, we should use Activity, Fragment scope like below.
class CoroutineActivity : AppCompatActivity() { // MainScope() = ContextScope(SupervisorJob() + Dispatchers.Main) private val scope: CoroutineScope = MainScope() override fun onDestroy() { super.onDestroy() coroutineContext.cancelChildren() } fun loadSomething() = scope.launch { // code }}
see more explanation about the GlobalScope.
Tip2: Always use SupervisorJob on Android
If you want to run the background task which would throw the exceptions, you may write the function like below
val job: Job = Job()val scope = CoroutineScope(Dispatchers.Default + job)fun backgroundTask(): Deferred<String> = scope.async { … }fun loadData() = scope.launch { try { doWork().await() } catch (e: Exception) { … }}
Unfortunately, it can’t catch the exception, so the app will crash. It is because the backgroundTask would create a child job and the failure of the child job leads to an immediate failure of its parent.
The solution is to use SupervisorJob .
val job: Job = SupervisorJob()val scope = CoroutineScope(Dispatchers.Default + job)fun backgroundTask(): Deferred<String> = scope.async { … }fun loadData() = scope.launch { try { backgroundTask().await() } catch (e: Exception) { … }}
and it only works if you explicitly run the async on the coroutine scope with SupervisorJob. It will still crash the app if the async is launched in the scope of parent coroutine like below.
val job: Job = SupervisorJob()val scope = CoroutineScope(Dispatchers.Default + job)fun loadData() = scope.launch { try { async { … }.await() // this is running in the scope of parent. } catch (e: Exception) { … } // Can’t catch the exception}
if you want to launch the async in the parent scope, you should also assign the coroutine context explicitly.
val job: Job = SupervisorJob()val scope = CoroutineScope(Dispatchers.Default + job)fun loadData() = scope.launch { try { async(scope.coroutineContext) { … }.await()
// Now the context is SupervisorJob. } catch (e: Exception) { … } // Can catch the exception now.}
Tip3: Assign the context explicitly for the suspend functions
If we want to make the function “suspendable”, we can just add the suspend for a function like below.
suspend fun doSomething(): Result { // Running for long time return result}
It seems fine, but sometimes it brings implicit errors especially on Android. For example:
suspend fun loadingSomething(): Result { loadingView.show() val result = withContext(Dispatchers.IO) { doSomething() } loadingView.hide() return result}
the loadingView.show() and loadingView.hide() must be running in main thread. Therefore it would crash the application like this
val scope = CoroutineScope(Dispatchers.Default + job)scope.launch { loadingSomething() // oops, it would crash the app.}
So the better way is that we assign the explicit dispatcher for the suspend functions
suspend fun doSomething(): Result = withContext(Dispatchers.IO) { // Running for long time return@withContext result}suspend fun loadingSomething(): Result =
withContext(Dispatchers.Main) { loadingView.show() val result = doSomething() loadingView.hide() return@withContext result}val scope = CoroutineScope(Dispatchers.Default + job)scope.launch { loadingSomething()
// safe, because loadingSomething would run in main thread.}
So if we assign the dispatcher explicitly, the function is safe and looks concise.
Tip4: Do not cancel the scope job directly.
Assume that we have implemented some job manager.
class JobManager {
private val parentJob = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
fun job1() = scope.launch { /* do work */ }
fun job2() = scope.launch { /* do work */ }
fun cancelAllJobs() {
parentJob.cancel()
}
}fun main() {
val jobManager = JobManager()
jobManager.job1()
jobManager.job2()
jobManager.cancelAllJobs()
jobManager.job1() // can't run the job here
}
And you would get the error at second job1(). It is because that when we cancel the parentJob , we put the parentJob into COMPLETED state. The coroutines launched in the scope of completed job won’t be executed.
Instead, we should use cancelChildren function to cancel the jobs.
class JobManager {
private val parentJob = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
fun job1() = scope.launch { /* do work */ }
fun job2() = scope.launch { /* do work */ }
fun cancelAllJobs() {
scope.coroutineContext.cancelChildren()
}
}fun main() {
val jobManager = JobManager()
jobManager.job1()
jobManager.job2()
jobManager.cancelAllJobs()
jobManager.job1() // No problem now.
}
Tip5: Use Android ktx for coroutine scope.
Android KTX has provided some useful extensions for coroutines. It can support lifecycle automatically so we don’t need to do the same thing again. The coroutineContext in lifecycleScope is SupervisorJob() + DispatchersMain.immediate and is cancelled when the lifecycle ended.
But if we need to run the suspended functions in the lifecyleScope. We should still need to assign the context as Tips3 mentioned. Especially the suspended functions include network calls.
lifecycleScope.launch {
doSomething()
}suspend fun doSomething = withContext(Dispatchers.IO) { // Network calls should not run in main thread}
Tip6: Use async.await() and withContext for different purpose.
If you want to wait for the result from other suspended functions, you have two choices.
val result = async { doSomething() }.await()
val result = withContext(Dispatchers.IO) { doSomething()}
So what is the difference between async.await and withContext??
Let’s check the source code.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->...}
By the implementation, the async returns a Deferred object and withContext return the value directly.
So the caller of withContext would suspend immediately and wait for the result. The caller of async WOULD NOT suspend immediately. Therefore we can have a conclusion
If you need to run the coroutines sequentially, use withContext
If you need to run the coroutines in parallel, use async
In fact, if you use async.await directly, IntelliJ IDEA would show a warning and suggest you use withContext directly.
Tip7: Use suspendCancellableCoroutine to wrap callback-based interfaces.
Before Kotlin Coroutines is out, there are many projects using the callbacks for async programming. If we want to use Kotlin Coroutines to call the async functions with callback, is there any way to support the callback-based API?
Yes, we can wrap the callback interface by suspendCancellableCoroutine. Here is the example for Android’s CameraDevice.StateCallback
suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
suspendCancellableCoroutine { cont ->
val callback = object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
cont.resume(camera)
}
override fun onDisconnected(camera: CameraDevice) {
cont.resume(null)
}
override fun onError(camera: CameraDevice, error: Int) {
// assuming that we don't care about the error in this example
cont.resume(null)
}
}
openCamera(cameraId, callback, null)
}
This pattern is extremely useful for Android applications because there are many callback-based API in Android frameworks.
Kotlin Coroutines patterns & anti-patterns
https://www.youtube.com/playlist?list=PLQkwcJG4YTCQcFEPuYGuv54nYai_lwil_
basic coroutines사용법, 매우 간단하고 쉽다. 각 10분 분량 1-9 , 10 완료
job.join()에서 실행되는 것이 아닌 GlobalScope.launch 에서 이미 실행
cancel했지만 그래도 아래와 같이 복잡한 연산을 하는 경우 멈추지 않고 계속 작동한다.
아래는 정확하게 원하는 시점이 아니지만 결국 멈추게 된 경우
아래는 좋은 방법은 아니다. async를 이용한 방법이 좋다.
아래는 안드로이드를 위한 lifecycleScope를 이용한 경우
아래는 안드로이드를 위한 lifecycleScope를 사용하지 않은 안좋은 예시
https://youtu.be/S-10lLA0nbk
https://youtu.be/F63mhZk-1-Y
package com.codingwithmitch.coroutineexamples import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button.setOnClickListener { setNewText("Click!") CoroutineScope(IO).launch { fakeApiRequest() } } } private fun setNewText(input: String){ val newText = text.text.toString() + "\n$input" text.text = newText } private suspend fun setTextOnMainThread(input: String) { withContext (Main) { setNewText(input) } } private suspend fun fakeApiRequest() { logThread("fakeApiRequest") val result1 = getResult1FromApi() // wait until job is done if ( result1.equals("Result #1")) { setTextOnMainThread("Got $result1") val result2 = getResult2FromApi() // wait until job is done if (result2.equals("Result #2")) { setTextOnMainThread("Got $result2") } else { setTextOnMainThread("Couldn't get Result #2") } } else { setTextOnMainThread("Couldn't get Result #1") } } private suspend fun getResult1FromApi(): String { logThread("getResult1FromApi") delay(1000) // Does not block thread. Just suspends the coroutine inside the thread return "Result #1" } private suspend fun getResult2FromApi(): String { logThread("getResult2FromApi") delay(1000) return "Result #2" } private fun logThread(methodName: String){ println("debug: ${methodName}: ${Thread.currentThread().name}") } }
위에서 getResult2FromApi는 getResult1FromApi 가 완료된후에 실행된다. 하나의 launch에서 여러개의 suspend function호출은 순차적으로 진행된다. 새로운 concurrent 를 이용해 처리하는 것이 아니다.
https://youtu.be/cu0_fHFQGbM
package com.codingwithmitch.coroutineexamples import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main class MainActivity : AppCompatActivity() { private val JOB_TIMEOUT = 2100L override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button.setOnClickListener { setNewText("Click!") CoroutineScope(IO).launch { fakeApiRequest() } } } private fun setNewText(input: String){ val newText = text.text.toString() + "\n$input" text.text = newText } private suspend fun setTextOnMainThread(input: String) { withContext (Main) { setNewText(input) } } private suspend fun fakeApiRequest() { withContext(IO) { val job = withTimeoutOrNull(JOB_TIMEOUT) { val result1 = getResult1FromApi() // wait until job is done setTextOnMainThread("Got $result1") val result2 = getResult2FromApi() // wait until job is done setTextOnMainThread("Got $result2") } // waiting for job to complete... if(job == null){ val cancelMessage = "Cancelling job...Job took longer than $JOB_TIMEOUT ms" println("debug: ${cancelMessage}") setTextOnMainThread(cancelMessage) } } } private suspend fun getResult1FromApi(): String { delay(1000) // Does not block thread. Just suspends the coroutine inside the thread return "Result #1" } private suspend fun getResult2FromApi(): String { delay(1000) return "Result #2" } }
======================================================================================================================
@synchronized @Volatile Lock Atomic primitives
multi thread에서 안전하게 사용하는 방법을 총정리한 블로그
https://proandroiddev.com/synchronization-and-thread-safety-techniques-in-java-and-kotlin-f63506370e6d
In order to force changes in a variable to be immediately visible to other threads, we can use the annotation @Volatile, a in the following example:
Java has the synchronized keyword, which can be applied to methods to ensure that only one thread at a time can access them. A thread that enters a synchronized method obtains a lock (an object being locked is the instance of the containing class) and no other thread can enter the method until the lock is released. Kotlin offers the same functionality with the @Synchronized annotation. When applied to a method, it will produce the same bytecode as Java would with the synchronized keyword:
@Synchronizedfun threadSafeFunction() {}
synchronized volatile kotlin’s Any Java's Object wait() notify() notifyAll() methods
https://blog.egorand.me/concurrency-primitives-in-kotlin/
Kotlin Coroutines patterns & anti-patterns
Coroutines사용시에 좋은 방법 안좋은 방법 예를 들어 보여줌
https://proandroiddev.com/synchronization-and-thread-safety-techniques-in-java-and-kotlin-f63506370e6d
Async Operations with Kotlin Coroutines — Part 1
Coroutines 전반에 관한 기초내용 - 나중에 읽어 볼만 하다고 생각
https://proandroiddev.com/async-operations-with-kotlin-coroutines-part-1-c51cc581ad33#:~:text=async%3A%20Async%20coroutine%20builder%20is,to%20get%20the%20eventual%20result.
Multi Selection RecyclerView in Android | Code Walkthrough
https://youtu.be/YogGlRYCfp0
cancel nested coroutine at once ( 중첩 coroutine을 한번에 cancel하는 방법 )
ref) https://codinginfinite.com/android-kotlin-coroutinescope-example/
class MyViewModel constructor(private val apiService : ApiService) : ViewModel(), CoroutineScope { // 1 // 2 private val job = Job() // 3 override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main // 4 fun executeCalls() { launch(context = coroutineContext) { val firstRequestDeferred = async { apiService.request1() } val secondRequestDeffered = async { apiService.request2() } handleResponse(firstRequestDeferred,await(),secondRequestDeffered.await()) } } // 5 override fun onCleared(){ job.cancel() } }
주의점은 Job이 cancel된 경우 해당 coroutine은 다시 사용할수 없는데 이런경우 다시 Job을 생성해 주어야 한다.
https://medium.com/l-r-engineering/launching-kotlin-coroutines-in-android-coroutine-scope-context-800d280ebd80