저번 글에서 예고했던 대로 메인 스레드에서 했던 무거운 작업을 이제 다른 스레드에서 작업시켜보자.
이전 동작 이해하기
아까 첨부한 링크에서의 이전 글을 보면
우리는 viewModelScope를 사용해 CPU 연산이 무거운 작업을 메인 스레드에서 실행했다.
그렇게 수초간 메인 스레드가 blocking되었고, 다른 UI는 반응할 수 없었다.
그리고 우리가 실행했던 viewModelScope의 Context를 확인하는 방법이 하나 있다.
fun performCalculationOnMain(factorial: Int) {
viewModelScope.launch {
println("Coroutine Context: $coroutineContext")
var result = BigInteger.ONE
val computationDuration = measureTimeMillis {
result = calculateFactorial(factorial)
}
var resultInString = ""
val conversionDuration = measureTimeMillis {
resultInString = result.toString()
}
println("calculation: $computationDuration, conversion: $conversionDuration")
}
}
이렇게 println을 사용해서 해당 코루틴의 Context를 확인할 수 있는데..
... [StandaloneCoroutine{Active}@1234567, Main[immediate]]
이 프린트문을 통해 해당 코루틴이 Active 상태임을 알 수 있고
메인 스레드에서 실행되고 있음을 확인할 수 있다.
그냥 viewModelScope에서 Dispatcher를 바꾼다면?
viewModelScope.launch(context = Dispatchers.Default) {
// ...
}
물론 이렇게 수정한다면 현재 예제에서는 괜찮을 수 있다.
하지만, 보통 연산 결과를 UI로 보여주기 위해서는 LiveData의 값을 setValue를 통해 변경해주게 되는데,
이 작업은 메인 스레드에서만 수행해야 하므로 위 코드는 적절하지 않다.
다른 스레드에서 실행하기 위해 withContext 사용하기
이제 우리가 전에 사용했던 calculateFactorial 함수를 변경해보자.
먼저 함수 본문을 withContext로 감싼다.
dispatcher를 지정한다.
그렇게 되면 빨간 밑줄이 withContext에 생기게 되는데
이는 코루틴 내부나 suspend function 내부에서 호출하지 않았다는 의미의 에러이다.
suspend fun calculateFactorial(number: Int): BigInteger {
withContext(Dispatchers.Default) {
var factorial = BigInteger.ONE
for (i in 1 .. number) {
factorial = factorial.multiply(BigInteger.valueOf(i.toLong()))
}
return factorial
}
}
이렇게 되면 return에서 에러가 발생하는데 withContext를 리턴해주어야 한다.
그리고 withContext 내부에서는
마지막 줄이 자동으로 return값이 되기 때문에 return 키워드를 지워주면 된다.
완성된 함수는 아래와 같다.
suspend fun calculateFactorial(number: Int): BigInteger {
return withContext(Dispatchers.Default) {
var factorial = BigInteger.ONE
for (i in 1 .. number) {
factorial = factorial.multiply(BigInteger.valueOf(i.toLong()))
}
factorial
}
}
해당 함수를 다른 문법으로도 정리할 수 있다.
suspend fun calculateFactorial(number: Int) = withContext(Dispatchers.Default) {
var factorial = BigInteger.ONE
for (i in 1 .. number) {
factorial = factorial.multiply(BigInteger.valueOf(i.toLong()))
}
factorial
}
}
이렇게 되면 withContext 내부에서 실행되는 코드 블럭은 Default Dispatcher를 통해 실행된다.
욕심내서 문자열 변환도 다른 스레드에서 실행하기
위에서 봤던 performCalculationOnMain 함수에서
연산 결과를 toString으로 문자열 변환하는 부분이 있었다.
해당 부분에 withContext를 사용해보자.
val conversionDuration = measureTimeMillis {
resultInString = withContext(Dispatchers.Default) {
result.toString()
}
}
이제 문자열 변환도 메인이 아닌 다른 스레드에서 실행될 것이다.
withContext는 정말 Context를 바꾼다!
내 예제에서는 Dispatcher만 변경해서 사용했지만 ExceptionHandler나 Job을 변경해서 사용할 수 있다.
하지만, 보통 Dispatcher이외의 Context 요소를 변경할 일은 거의 없다고 한다.
그럼에도 불구하고 코루틴 이름을 같이 변경한다면 + 연산자를 사용해 동시에 변경할 수 있다.
withContext(Dispatchers.Default + CoroutineName("String Converter")) {
// ...
}
물론 당연하게도 같은 요소를 두 번 변경할 수 없다.
withContext(Dispatchers.Default + Dispatchers.IO) {
// ...
}
가령 위와 같은 코드는 에러가 발생한다.
'Language > Kotlin' 카테고리의 다른 글
Java, Kotlin Interoperability 해결하기 (0) | 2024.11.18 |
---|---|
CoroutineScope와 CoroutineContext 되짚어보기 (0) | 2024.09.21 |
Coroutines Dispatcher 알아보기 (2) | 2024.09.13 |
CoroutineContext와 CoroutineScope 알아보기 (1) | 2024.09.04 |
Coroutines on Main Thread(위험성에 대해 인지하기) (0) | 2024.09.03 |