์๋๋ก์ด๋ ์คํ์ฑํ ๋ฐฉ์์ ๋ณด๊ฒ ๋์ด ์ฐพ์๋ณธ ์ฃผ์ ๋ค.
CompletableDeferred
์ฝ๋ฐฑ ๊ธฐ๋ฐ์ ๋น๋๊ธฐ ์์ ์ ์ํํ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์๋ ํด๋์ค๋ค.
Deferred ํด๋์ค ๊ธฐ๋ฐ์ด๋ผ ๋ฐํ ๊ฐ์ ํ์ฉํ ์ ์๋๋ฐ, ์ธํฐํ์ด์ค๋ฅผ ๋จผ์ ๋ณด์.
public interface CompletableDeferred<T> : Deferred<T> {
public fun complete(value: T): Boolean
public fun completeExceptionally(exception: Throwable): Boolean
}
์ด ๋ ์ฌ์ฉ๋๋ complete๋ ์์ ์ฝ๋ฃจํด ๊น์ง ์ ํ๋๊ธฐ ๋๋ฌธ์ ์ฌ๋ฌ๊ฐ์ ์ฝ๋ฃจํด์ผ๋ก ์ค์ฒฉ๋์ด์์ผ๋ฉด ๋ชจ๋ ์์ ์ด ๋๋์ผ complete๊ฐ true๋ฅผ ๋ฐํํ๋ค. ์์ ์ด ๋๋์ง ์์ ๊ฒฝ์ฐ๋ ์์ ์ด ๋๋ ๋ ๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค. complete์์ value๊ฐ์ผ๋ก ์๋ฃ๋ฅผ ๊ฒฐ์ ํ๋ค. completeExceptionally๋ ์ด๋ฆ์์ ์ ์ ์๋ฏ์ด ์์ธ๊ฐ ํธ๋ฆฌ๊ฑฐ๋ค.
suspend fun fetchDataFromServer(): Data {
val deferred = CompletableDeferred<Data>()
someAsyncOperation { data ->
// ... ์์
๋ค
deferred.complete(data)
}
return deferred.await()
}
CompletableDeferredํด๋์ค๋ก ๊ฐ์ฒด๋ฅผ ํ๋ ๋ง๋ค๊ณ , ๋น๋๊ธฐ ์์ ๋ธ๋ก์์ ์ด ๋ณ์๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ์ ์ดํ ์ ์๊ฒ๋๋ค. ํด๋น ๋ธ๋ก์ ์์ ์ด ๋ชจ๋ ๋๋๋ฉด complete๊ฐ ๋ถ๋ฆฌ๊ณ , ๊ทธ ์์ ๋ด๊ธด ๊ฐ์ deffered.await()๋ก ๊บผ๋ด ์ธ ์ ์๋ค.
์ด์ ์ ๊ทธ๋ฅ ์ฌ์ฉํ๋ suspendable coroutine(async(์๋ ๋ฐํ๊ฐ์ด Deferred๋ผ์ await๊ฐ ๊ฐ๋ฅํ๋ค), delay๊ฐ์) ๊ฒ ๊ณผ ์ฐจ์ด์ ์ด ๋ฐ๋ก ๋ณด์ธ๋ค๋ฉด ์ฝ๋ฃจํด ๋ง์คํฐ๋ผ๊ณ ํ ์ ์๊ฒ ๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉํ๋ async ๊ฐ์๊ฑด ๋ฐ๊นฅ์ ์๋ ๋ณ์๋ก ๋ด๋ถ ์ฝ๋ฃจํด์ ์ ์ดํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํ๋ค. ๊ทธ๋ฐ๋ฐ ์ด ์ฝ๋๋ฅผ ๋ณด๋ฉด, ๋น๋๊ธฐ ์์ ์ ํ ์ฝ๋ฃจํด ์ค์ฝํ ๋ฐ๊นฅ์ ๊ฐ์ฒด๋ฅผ ์ ์ธํด์ ๊ฐ์ ๊ฐ์ํ๋ฉฐ ๊ฒฐ๊ณผ๊ฐ๋ ๊ฐ์ ธ์จ๋ค๋ ์ฐจ์ด์ ์ ๋ณผ ์ ์๋ค.
๊ทธ๋ผ ์๋ฌธ์ ์ด ํ๋ ์๊ธด๋ค. async๋น๋๋ฅผ ์ฐ๋ฉด ๋ ๊ฐ๋จํ๋ฐ ์ ๊ตณ์ด CompletableDeferred๋ฅผ ์จ์ผํ๋? async ๊ณ์ ์จ๋ ๋๋ค. ์ฌ์ฉ๋ฒ ๊ฐ๋จํ๊ณ await๋ก ๊ฐ ๋ฐ์์ฌ ์ ์๋ค. ๊ทธ๋ฐ๋ฐ CompletableDeferred๋ฅผ ์ฌ์ฉํ๋ฉด ์๋์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ์ค์ ํด์ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ์ข ๋ ์ ๊ตํ๊ฒ ๊ฐ์ ๋ค๋ฃฐ ์ ์๊ณ , ์์ ์ด ์ทจ์๋ ๋ ์ํํ ๋ด์ฉ์ ๋์ฑ ์์ธํ๊ฒ ์ง์ ํ ์ ์๊ฒ๋๋ค.
์ข ๋ ์์๋ฅผ ๋ค์ด๋ณด๊ฒ ๋ค. ์ฌ๋ฌ๊ฐ์ api ๊ฒฐ๊ณผ๊ฐ์ ์กฐํฉํด์ ์ฌ์ฉํด์ผ๋๋ค๊ณ ๊ฐ์ ํด๋ณด์.
suspend fun fetchDataFromServer(): String {
val deferred1 = CompletableDeferred<String>()
val deferred2 = CompletableDeferred<String>()
viewModelScope.launch {
val result1 = fetchData1()
deferred1.complete(result1)
}
viewModelScope.launch {
val result2 = fetchData2()
deferred2.complete(result2)
}
// ๋ ์์
์ด ๋ชจ๋ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆผ
val data1 = deferred1.await()
val data2 = deferred2.await()
return "$data1 and $data2"
}
data1์ ๋ฐ์์ฌ ๋๋ deferred1์ด ๋๋์ผ ๊ฐ์ ๊ฐ์ ธ์ค๊ณ , data2์ญ์ deferred2๊ฐ ๋๋์ผ ๊ฐ์ ๊ฐ์ ธ์จ๋ค. ์๋ฃ๊ฐ ๋๋ ์์ ์ ์ด๋ค ๊ฐ์ ๊ฐ์ ธ์ฌ์ง ๋ช ์ํ๋ ๊ฒ ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ ์ฑ์ด ์ข์์ง๊ณ , ๊ฐ์ฒด์ด๋ฆ์ ์์ ์ด๋ฆ์ผ๋ก ๋ฐ๊ฟ์ค๋ค๋ฉด ๋ ์ฝ๊ธฐ ์ข๊ฒ ๋ฐ๊ฟ ์ ์๊ฒ๋๋ค.
val deferred1 = viewModelScope.async { fetchData1() }
val deferred2 = viewModelScope.async { fetchData2() }
deferred๋ถ๋ถ๋ง async๋ก ๋ฐ๊พผ ์ฝ๋๋ค. fetchData๊ฐ ์ํ๋๊ณ ๋์ ์ด๋ค ๊ฐ์ด ๋ฐํ๋ ์ง ์ ์ ์๋ค. ๋จ์ํ ์ด๋ ๊ฒ๋ง ๋๊ณ ๋ด๋ ์ฅ์ ์ด ๋ณด์ด๋๋ฐ, api๋ฅผ ๋์ฑ ๋ง์ด ์ฌ์ฉํ๊ฒ ๋๋ฉด ์ด ์ฅ์ ์ด ๋ ๋ถ๊ฐ๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ๋ค.
suspendCancellableCoroutine
์์ ์๊ธฐํ ComletableDeferred์ ๊ฑฐ์ ๋์ผํ ๊ฐ๋ ์ด๋ค. ์ฐจ์ด์ ์ผ๋ก๋ suspendCancellableCoroutine์ด ์ฝ๋ฃจํด ๋น๋๋ผ๋ ๊ฒ์ด๋ค.
suspend fun <T> suspendCancellableCoroutine(
block: (CancellableContinuation<T>) -> Unit
): T
์ฝ๋ฃจํด ๋ณธ๋ฌธ์ CancellableContinuation์ผ๋ก ๋ฐ๊ธฐ ๋๋ฌธ์ ์ด ์ค์ฝํ ์์์ ์์ ์ ์ํํ ๋ ์ทจ์์ ๋ํ ํ๋์ ์ ์ํ ์ ์๋ค. ์์ ์ด ์๋ฃ๋์๋ค๋ฉด resume, ์์ ์ค ์์ธ๊ฐ ๋ฐ์ํ๋ค๋ฉด resumeWithException์ผ๋ก ์ ์ด๊ฐ๋ฅํ๋ค. ๊ฐ์ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ์ด๊ฒ ์ญ์ ์ฝ๋ฐฑ๊ธฐ๋ฐ์ ๋น๋๊ธฐ์์ ์ ์ ํฉํ๋ค.
suspend fun fetchData(): String {
return suspendCancellableCoroutine { continuation ->
val job = CoroutineScope(Dispatchers.IO).launch {
try {
continuation.resume("User data")
} catch (e: Exception) {
continuation.resumeWithException(e)
}
}
continuation.invokeOnCancellation {
job.cancel()
}
}
}
job์ผ๋ก ์ง์ ํ ์์ ์ด ์๋ฃ๋๋ฉด, continuation์ resume์ ๊ฐ์ ์ ๋ฌํด์ fetchData์ ๋ฐํ๊ฐ์ผ๋ก ์ฌ์ฉํ๊ณ , ์์ ์ค์ ์์ธ๊ฐ ๋ฐ์ํ๋ค๋ฉด resumeWithException์ ์ฌ์ฉํด ์์ ์ค์ด๋ ์ฝ๋ฃจํด ์ธ๋ถ๋ก ์ ํ์ํฌ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ invokeOnCancellaion ๋ธ๋ก์๋ ์ทจ์๊ฐ ๋ฐ์ํ ๋ ์ํํ ์์ ์ ์ ์ํ ์ ์๋ค.
resumeWithException์ ์์ธ๋ฅผ ์ ํ์ํค๊ธฐ ๋๋ฌธ์ ์์์ฝ๋ ๊ธฐ์ค์ผ๋ก fetchData๋ฅผ ํธ์ถํ๋ ์ชฝ์์ ์์ธ์ฒ๋ฆฌ๋ฅผ ํด์ค์ผํ๊ณ , invokeOnCancellation์ ์ธ๋ถ์์ suspendCancellableCoroutine์ ๋ํ ์ทจ์๊ฐ ๋ฐ์ํ์ ๋ ์ด์ ์ ๋ ฅ๋ ๊ฐ์ ๋ ๋ฆฐ๋ค๋๊ฐ ํ๋ ์ด๊ธฐํ ๋ก์ง์ ๋ฃ์ด๋๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์ด๋ Cancellable์ ์๋ฏธ๊ฐ ์ค์ํ๋ค. ์ด๋ฆ์ ๋ค์ด์๋ Cancellable์ ์ด ์ฝ๋ฃจํด ์ค์ฝํ๋ฅผ ์ธ๋ถ์์ ์ทจ์์ํฌ ์ ์๋ ์ง์ ๋ํ ์ฌ๋ถ๋ฅผ ์๋ฏธํ๋ค. ์ฆ ์ธ๋ถ์์ ์์ธ๋ฅผ ๋ฐ๊ณ -> ์ทจ์๋ฅผ ํธ์ถํ๋ฉด invokeOnCancellation ๋ด๋ถ์ ์์ฑํด๋ ์ฝ๋๊ฐ ์คํ๋๋ค.
try-catch ๋์ runCatching์ ์ฌ์ฉํด ์ข ๋ ์ฝํ๋ฆฐ์ค๋ฝ๊ฒ ๋ฐ๊ฟ๋ณด๋ฉด
suspend fun fetchData(): String {
return suspendCancellableCoroutine { continuation ->
val job = CoroutineScope(Dispatchers.IO).launch {
val result = runCatching {
"User data"
}
result.onSuccess { data ->
continuation.resume(data)
}
result.onFailure { e -> // Throwable์ด ๋ค์ด์จ๋ค.
continuation.resumeWithException(e)
}
}
continuation.invokeOnCancellation {
job.cancel()
}
}
}
์ด๋ ๊ฒ ๊ฐ๋ ์ฑ ์ข๊ฒ ๋ฐ๊ฟ ์ ์๋ค.
onSuccess๋ฅผ ์ฐ์ง์๊ณ ๊ทธ๋ฅ ๋ฆฌ์๋ฒ๋ก api๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ๋ ์๋๋ฐ, ๊ทธ๋ ๊ฒ ํ๋ฉด ์ฑ๊ณตํ์ ๋ ๋ฐ์ดํฐ๋ฅผ this๋ก๋ง ์ ๊ทผํ ์ ์๋ค. ๋๋ ๋ฐ์์ฌ ๊ฐ์ฒด ๋ฐ์ดํฐ์๋ค๊ฐ ์ด๋ฆ์ ์ง์ ํ๋ ๊ฒ ๊ฐ๋ ์ฑ์ด ์ข๋ค๊ณ ์๊ฐํด์ onSuccess๋ฅผ ์ฌ์ฉํ๋ ๊ฑธ ์ ํธํ๋ค.
๋์์ด ๋๋ค๋ฉด ๋๊ธ์ด๋ ๊ณต๊ฐ ๋ฒํผ ํ ๋ฒ์ฉ ๋๋ฅด๊ณ ๊ฐ์ฃผ์ธ์!
'Android ๐ฅ๏ธ > Coroutine๐' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
emit๊ณผ value assignment ์ฐจ์ด์ (feat. SharedFlow, StateFlow) (0) | 2024.08.02 |
---|---|
Coroutine exceptions handling (0) | 2024.03.29 |
Asynchronous Flow - Intermediate flow operators~ (1) | 2024.03.29 |
Coroutine context and dispatchers (0) | 2024.03.29 |
Coroutine Composing suspending functions (0) | 2024.03.29 |