Coroutine - Chapter1

HEETAE HEO·2022년 7월 8일
0
post-thumbnail

Coroutine에 대해서 자세하게 글을 작성해보려고 합니다. 총 5가지의 챕터로 나눠 작성할 예정이며

다음과 같이 분류될 것입니다.

Chapter

  1. 코루틴의 기본과 구조

  2. 코루틴 빌더

  3. Cancellation and Exception Handling

  4. suspending 함수 구성

  5. CoroutineScope, CoroutineContext and Dispathers

으로 나눠 글을 작성해보려고 합니다.

What are Coroutines

어플리케이션을 실행시킨다면 Main Thread가 생성되며 어플리케이션 주기에 맞춰서 동작하게 됩니다.
이렇게 생성된 Main Thread는 매우 작거나 가벼운 동작만을 담당하여 동작합니다. 예를 들면 UI 작용같은 Button을 누르는 동작같은 것들을 말입니다.

그러다가 무거운 동작을 하게된다면 어떻게 될까요?(네트워크 동작, 파일 다운로드, 데이터베이스 작성 등)
이러한 동작들을 Main Thread에서 동작하게되면 block이 되어질 것입니다. Main Thread는 5초이상 blocked이 되어지면 어플리케이션은 꺼지게 됩니다. crash 메세지와 함께 말입니다.

그렇기에 이러한 무거운 동작들은 Main Thread로 처리하려고 하는 것은 잘못된 생각입니다. 이러한 동작을 처리하는 것은 다른 스레드를 생성하여 무거운 작업들을 맡기는 것 입니다. 그러한 작업들을 하는 Thread를 Background Thread라고 부릅니다. 그렇게 생성한 Background Thread에게 하나씩 일을 주는 것입니다.

1번 백그라운드 스레드에는 네트워크 통신작업을 2번 백그라운드 스레드에는 파일 다운로드를 3번에는 이미지 로딩을 말입니다. 그런데 이렇게 하나의 일을 줄때 마다 Background Thread를 생성하는게 맞을까요?
Thread를 생성하는 굉장히 많은 비용을 사용합니다. 그리고 Memory의 부족으로 어플리케이션의 동작이 멈출수도 있습니다. 이러한 문제를 해결할 수 있는 것이 지금 작성하고 있는 Coroutine을 사용한다면 해결할 수 있습니다.

하나의 Background Thread에서 Coroutine을 이용해 작업을 나누는 것입니다.

1번 Background Thread에서 3가지의 작업을 수행하는 것입니다. 네트워크 통신도 하고, 파일 다운로드도 하고 , 이미지 로딩도 하고 말입니다.

그렇다면 Coroutine을 어떻게 정의할 수 있을까요?

코루틴을 사람들은 경량 Thread라고 부르는데 그렇다고 Thread라는 것은 아닙니다 다르지만 비슷한 동작을 하기에 사람들은 경량 Thread라고 부르는 것입니다. 코루틴은 동시성을 가지고 있습니다. 동시성이란 여러 작업을 순차적을 정리해놓고 123번 작업들을 이동하면서 쭉 나아가는 것입니다. 이 작업들을 전환하는 속도가 매우 빠르기에 코루틴을 사용하면 사람들은 작업을 동시에 수행을 하는 것처럼 느껴지기에 코루틴을 쓰면 멀티테스킹이 가능하다라고 인지하는 분들이 계시기도 합니다.

또한 코루틴을 생성하는 거의 비용이 발생하지 않고 메모리에도 문제가 되지않습니다. 그렇기에 수만개의 코루틴을 생성하여도 메모리에 대한 걱정을 할 필요가 없습니다.

How to use Coroutines

fun main(){  //Executes in mainThread

	println("Main program starts : ${Thread.currentThread().name}")
    
    // some other code
    
    println("Main program ends : ${Thread.currentThread().name}")

}

을 수행하면 결과는 다음과 같이 나옵니다.

Main program starts : main
Main program ends : main

다음과 같이 결과가 나옵니다. Main Thread가 println을 처리했다는 뜻입니다.

이제 Background Thread를 사용해보겠습니다.

fun main(){  //Executes in mainThread

	println("Main program starts : ${Thread.currentThread().name}")
    
    thread { //creates a background thread(worker thread)
    	println("Fake work starts: ${Thread.currentThread().name}")
        Thread.sleep(1000) // pretend doing some work... may be file upload
        println(Fake work finished : ${Thread.currentThread().name}")
    	
    }
    
    println("Main program ends : ${Thread.currentThread().name}")

}

결과는 다음과 같이 나옵니다.

Main program starts : main
Main program ends : main
Fake work starts : Thread-0
Fake work finished : Thread-0

Thread-0이 Background Thread입니다.
Main Thread는 Background가 실행된다고 block되지 않기에 계속 실행됩니다.

fun main(){  //Executes in mainThread

	println("Main program starts : ${Thread.currentThread().name}")
    
    GlobalScope.launch{ // creates a background coroutine that runs on a background thread
    	println("Fake work starts: ${Thread.currentThread().name}")
        Thread.sleep(1000) // pretend doing some work... may be file upload
        println(Fake work finished : ${Thread.currentThread().name}")
    }
    
    println("Main program ends : ${Thread.currentThread().name}")

}

결과는 다음과 같습니다.

Main program starts : main
Main program ends : main

왜 GlobalScope안에 있는 println은 출력되지 않았을까요?
그것은 Main Thread가 코루틴이 동작하기전에 작업을 종료해버렸기 때문입니다.

fun main(){  //Executes in mainThread

	println("Main program starts : ${Thread.currentThread().name}")
    
    GlobalScope.launch{ // creates a background coroutine that runs on a background thread
    	println("Fake work starts: ${Thread.currentThread().name}")
        Thread.sleep(1000) // pretend doing some work... may be file upload
        println(Fake work finished : ${Thread.currentThread().name}")
    }
    Thread.sleep(2000)
    println("Main program ends : ${Thread.currentThread().name}")

}

이렇게 Main Thread의 동작을 코루틴 동작이 끝난다음 종료할 수 있게 작성해주고 동작을 하게되면 결과는 다음과 같습니다.

Main program starts : main
Fake work starts : DefaultDispatcher-worker-1
Fake work finished : DefaultDispatcher-worker-1
Main program ends : main

다음과 같이 결과가 출력이됩니다. 여기서 알 수 있는 것은 다음과 같습니다.

코루틴의 작업이 종료되기전에 Main Thread가 종료되면 코루틴은 동작을 멈추게 된다는 것을 말입니다.

sleep()을 사용하게 되면 Thread를 block하여 다른작업 또한 수행할 수 없게 하지만 coroutine에서는
delay()라는 함수를 쓰면 block은 되지 않고 free상태로 변경되어 다른 suspend함수를 처리하러 갑니다.

fun main(){  //Executes in mainThread

	println("Main program starts : ${Thread.currentThread().name}")
    
    GlobalScope.launch{ // creates a background coroutine that runs on a background thread
    	println("Fake work starts: ${Thread.currentThread().name}")
        delay(1000) // pretend doing some work... may be file upload
        println(Fake work finished : ${Thread.currentThread().name}")
    }
    Thread.sleep(2000)
    println("Main program ends : ${Thread.currentThread().name}")

}

이렇게 변경을 하여도 결과 값은 같습니다.

Main program starts : main
Fake work starts : DefaultDispatcher-worker-1
Fake work finished : DefaultDispatcher-worker-1
Main program ends : main

What is suspend modifier? And What are suspending functions?

  • suspend가 붙어 있는 함수는 suspeding 함수라고 알려져있습니다.

  • suspending 함수는 오직 코루틴 또는 다른 suspending 함수에서만 호출할 수 있습니다.

  • 하지만 외부의 코루틴에서는 부를 수 없습니다.

이 3가지는 알아야할 suspend 함수 특징입니다.

fun main(){  //Executes in mainThread

	println("Main program starts : ${Thread.currentThread().name}")
    
    GlobalScope.launch{ // creates a background coroutine that runs on a background thread
    	println("Fake work starts: ${Thread.currentThread().name}")
        delay(1000) // pretend doing some work... may be file upload
        println(Fake work finished : ${Thread.currentThread().name}")
    }
    runBlocking { // Creates a coroutine that blocks the current main thread
    	delay(2000) // wait for coroutine to finish
    }
    println("Main program ends : ${Thread.currentThread().name}")

}

이렇게 작성을 하게 되면 coroutine이 동작을 끝날 때 까지 기다리기 때문에 코루틴 내부 동작을 마치고 Main Thread가 종료하게 됩니다. 하지만 runBlock은 이름에서도 알 수 있듯이 Thead를 block하는 방법이기에 올바르지 않은 방법입니다. 그렇기에 다음과 같이 사용을 합니다.

fun main(){  //Executes in mainThread
	
    runBlock {
	println("Main program starts : ${Thread.currentThread().name}")
    
    GlobalScope.launch{ // Thread : T1
    	println("Fake work starts: ${Thread.currentThread().name}") // Thread : T1
        delay(1000) // Coroutine is suspend but Thread : T1 is free (not blocked)
        println(Fake work finished : ${Thread.currentThread().name}")
    }
    	delay(2000) // main thread : wait for coroutine to finish
    
    println("Main program ends : ${Thread.currentThread().name}") //main

}

결과는 다음과 같습니다.

Main program starts : main
Fake work starts : DefaultDispatcher-worker-1
Fake work finished : DefaultDispatcher-worker-1
Main program ends : main
fun main(){  //Executes in mainThread
	
    runBlock {
	println("Main program starts : ${Thread.currentThread().name}")
    
    GlobalScope.launch{ // Thread : T1
    	println("Fake work starts: ${Thread.currentThread().name}") // Thread : T1
        delay(1000) // Coroutine is suspend but Thread : T1 is free (not blocked)
        println(Fake work finished : ${Thread.currentThread().name}")
    }
    	mySuspendFunc(2000) // main thread : wait for coroutine to finish
    
    println("Main program ends : ${Thread.currentThread().name}") //main

}

suspend fun mySuspendFunc(time : Long) {
	// code..
    delay(time)
}

다음과 같이 사용할 수 있습니다. 코루틴안에서 suspend 함수를 넣어주는 것입니다.

이렇게 Chapter1를 마치고 다음 Chapter2로 찾아오겠습니다.

읽어주셔서 감사합니다.

profile
Android 개발 잘하고 싶어요!!!

0개의 댓글