BigJeon Android 개발 블로그
비동기 처리를 쉽게 해주는 Coroutine에 대하여 알아보자_1 본문
지난번 포스팅에서는 Coroutine을 사용하기 위한 사전 공부로 Scope함수를 알아보았습니다.(Scope함수란?)
지난번 포스팅에 나와있듯이 Scope함수는 범위를 나타내는데 이를 토대로 Coroutine에 대하여 예제와 함께 알아보도록 하겠습니다.
1.Coroutine이란 무엇일까?
비동기식의 프로그래밍과 동기식 프로그래밍의 데이터 처리 속도는 상당히 많이 발생하기때문에 꼭 알아두어야 하는데요, 예전의 비동기식 프로그래밍은 복잡 그 자체였습니다. 하지만 이를 쉽게 간으하게 만들어 주기 위해 나온것이 바로 이 '코루틴'입니다.
코루틴이란 이름 때문에 코틀린언어 에서만 한정적으로 쓰이는 코틀린 언어라고 생각 할 수 있는데,
코틀린 뿐만아니라 다른 언어들에서도 사용되는 비동기 작업 처리를 쉽고 빠르게, 가독성이 좋게 만들 수 있게 해주는 루틴중에 하나입니다.
이렇게만 말하면 무슨 역활을 하는지 잘 느껴지지 않기에 예시를 들어 보겠습니다.
var num1 : Int = 0
for (i in 1..10){
num1 += i
}
var num2 : Int = 0
for (i in 1..10){
num2 += i
}
println("$num1 , $num2")
실행 결과 : 55 , 55
우선 기존의 동기식 방법을 이용하여 1부터 5까지 더하는 함수를 2개 생성해보았습니다.
위의 코드이 작업 순서를 따라가보면,
우선 num1이라는 변수에 대한 작업을 다 끝내고 num1의 작업이 종료 되면 그때서야 num2에 대한 작업을 수행해 결과값을 업게되는데
이를 동기식 처리라 부릅니다.
즉, 동기식 처리란 맨위의 코드부터 순차적으로 한가지일을 끝내고 다음일을 시작하고 또 그 작업이 완료되면 그 다음 작업을 수행하는 한번에 한가지씩 또한, 해당 작업을이 끝날때까지 해당 작업만을 진행하는 방법입니다.
이제 코루틴을 이용한 비동기식 루틴을 작성하여 똑같은 작업을 수행시켜 보겠습니다.
GlobalScope.launch (Dispatchers.Main){
val job1 = async (Dispatchers.IO) {
var total = 0
for (i in 1..5) {
total += i
delay(1000)
println("job1")
}
total
}
val job2 = async (Dispatchers.Main){
var total = 0
for(i in 1..5){
delay(1000)
println("job2")
total += i
}
total
}
val result1 = job1.await()
val result2 = job2.await()
println("result1 : $result1, result2 : $result2")
}
실행 결과 :
job1
job2
job1
job2
job1
job2
job1
job2
result1 : 15, result2 : 15
이해를 쉽게 하기위해 for문을 돌때마다 한번씩 어느 부분에 대한 작업을 하는지 출력을 넣어봤습니다.
(Dispatchers.Main, IO의 경우 각각 UI를 바꾸는 부분, 연산처리를 하는 부분을 표현합니다.)
위의 예제에서 알 수 있듯이 동기식 프로그램은 job1을 완료한후 job2를 수행하는데, 비동기식 처리인 Coroutine의 경우
job1에서 가장 처음 수행하는 일 1을 더하는 수행을 하고 바로 2를 더하는게 아닌 job2로 넘어가 job2에 1을 더해주는 작업을 하게됩니다.
이후 다시 job1로 돌와와 1은 작업을 수행했으니 2를 더하는 작업을 수행하고, 해당 작업이 끝나면 다시 job2로 넘어가 2를 더해주는 작업을 하게 됩니다.
결과에서 알 수 있듯이,
비동기식 처리란 : 한번에 여러가지일을 빠르게 왔다갔다 하면서 동시에 일을 하는것
코루틴 이란 : 비동기식 코드를 작성을 쉽게, 코드의 가독성을 높이고 빠른 작성을 가능하게 해주는 비동기식 처리 방법중 하나의 루틴
이라고 알수있습니다.
코루틴은 GlobalScope.launch로 정의되며, {...} 내부의 코드가 비동기적으로 실행됩니다.
코루틴이 무엇인지, 비동기 처리가 무엇인지 동기와 비동기의 작업 처리 방법이 어떤 부분에서 차이가 있는지에 대하여 알아보았으니,
코루틴을 사용하는 몇몇 방법들을 예제와 함께 알아보겠습니다.
2.Coroutine의 사용 예시
우선 가장 기본적인 코루틴으로 작성된 작업을 완료 할때까지 기다렸다, 완료후 다음 코드를 진행하는 사용 방법을 알아보겠습니다.
예제 코드는 다음과 같습니다.
.await()이용하여 작업 완료 시 까지 기다리기
GlobalScope.launch {
launch {
println("Start")
}
var value = async {
var total = 0
for (i in 1..10) total += i
total
}.await()
println("Finish : $value")
}
실행 결과 :
Start
Finish : 55
.await()을 이용하면 해당 코루틴 안의 작업을 마무리하고 마무리가 된 이후에 다음 코드를 실행시킬수 있습니다.
await()코드만 제거하고 실행 하면 결과값이 Finish : DeferredCoroutine{Active}@cc1a529로 나오는데,
total값 처리가 마무리되기전에 마지막 Finish부분이 실행되어 이전과 다른값이 나오는걸 확인 할 수 있습니다.
.await()을 이용하지 않고 작업 완료 시 까지 기다리기
GlobalScope.launch (Dispatchers.IO){
val value = withContext(Dispatchers.Main){
var total = 0
for (i in 1..10){
total += i
}
total
}
println("result : $value")
println("Finish")
}
실행 결과 :
result :
55
Finish
withContext()를 사용하면 해당 작업을 완료한 뒤 다음 작업을 수행하는데, 이 또한 withContext()를 지우고 실행해보면
result : StandaloneCoroutine{Active}@57c00ae
Finish
라는 결과값을 받게 됩니다.
해당 결과 역시 결과값 처리가 되기 전에 동시에 result값과, Finish문구를 출력하게 되어 생기는 결과 값 입니다.
코루틴의 장점으로 원하는 조건, 시간에 쉽게 작업을 중지시키고, 다시 작동시키는게 가능한데요.
우선 작업을 종료시키는 방법을 알아보겠습니다.
runblocking을 이용해 코루틴 중지 시점 정하기
runblocking의 경우 보통 테스트코드 작성시 사용되고 Tread를 제어한다는 특징이 있습니다.
.cancel() / runBlocking{}
val job = GlobalScope.launch() {
repeat(10){
delay(1000)
println("Job...Working...")
}
}
runBlocking {
delay(3000)
job.cancel()
}
println("Finish")
실행 결과 :
Job...Working...
Job...Working...
Job...Working...
Finish
이처럼 특정 코루틴 작업에 대하여 runBlocking{}을 이용해 일정 시간 혹은 조건 동안만 진행시키고 .cancel 시길 수 있습니다.
하지만 runBlocking은 작업을 중지 시킬 순간을 정해주기 위함이지 중간에 종료시키키 위한 것 만은 아닙니다.
이번엔 runBlocking을 이용해 작업이 완료 될 때까지 기다리는 방법을 알아보겠습니다.
.join() / runBlocking{}
val job = GlobalScope.launch() {
repeat(5) {
delay(1000)
println("Job...Working...")
}
}
runBlocking {
delay(3000)
job.join()
}
println("Finish")
실행 결과 :
Job...Working...
Job...Working...
Job...Working...
Job...Working...
Job...Working...
Finish
.join을 이용하면 원하는 특정 코루틴의 작업이 완료 될 때까지 기다리고 작업이 완료된 후에 다음 작업을 시작 하는것을 알 수 있습니다.
.withTImeout()
다음으로는 코루틴 내부의 작업이 끝나지 않아도 중간에 종료 시켜주는 방법인데요,
var job = GlobalScope.launch {
withTimeout(3000) {
repeat(10) {
delay(1000)
println("Job Working....")
}
}
}
println("Finish")
실행 결과 :
Finish
Job Working....
Job Working....
위와 같이 withTimeout()을 통해 3000이라는 값동안만 작업을 하게 만들어 10번 작업해야하는 작업을 2번만 하고 끝난것을 볼 수 있습니다. 또한 await()이나, join등을 쓰지않아 코루틴이 동작하는동안 Finish가 먼저 출력되어 나온것도 확인 할 수 있습니다.
지금까지 알아본 코루틴 함수로 Async함수일떄 사용하는 .await / Tread를 제어하는 join, cancel / 주로 테스트 코드 작성시 사용되는 runBlocking{} / 작업이 완료되지 않아도 종료 할 수 있는 withTimeout() 등을 알아보았는데요.
launch로 시작한 함수의 경우 해당 블록을 제어 할 수 있지만, 작업 후의 결과를 반환하지 않고,
Async로 시작함 함수의 경우 작업이후 결과값을 반환한다는 점에서 차이점을 찾을 수 있습니다.
이번 Coroutine에 대한 포스팅은 여기서 마치고 다음 포스팅에 이번 포스팅과 이어서 Channel이라는 메서드를 사용하는 방법에 대하여 알아 보도록 하겠습니다!
*잘못된 정보에 대한 지적은 항상 감사한 마음으로 받겠습니다.*
'안드로이드(기술)' 카테고리의 다른 글
Android DI - Hilt(1) (0) | 2024.06.14 |
---|---|
비동기 처리를 쉽게 해주는 Coroutine에 대하여 알아보자_2(최종) (0) | 2021.10.03 |
아키텍처가 대체 뭘까? (0) | 2021.09.09 |
안드로이드 아키텍쳐 컨포넌트(AAC)란? 1)lifecycle (0) | 2021.05.25 |
안드로이드 4대 컴포넌트란? (0) | 2021.05.21 |