[안드로이드 코루틴(Coroutines) 1] - 초기설정과 실행

이영준·2022년 10월 26일
0

대부분의 스마트폰은 60hz 이상의 주사율, 즉 1초에 60개 이상의 리프레시된 화면을 보여준다. 메인 스레드에서 적게는 8ms마다 화면을 리프레시를 하므로 비동기적으로 작업하는 것이 필요하고, 이를 위해 사용하는 것이 코루틴이다.

어떻게 보면 여러 작업을 병렬적으로 하기 위한 스레딩과 비슷해 보이지만, 한 스레드에서 여러 개의 코루틴을 가질 수 있다는 점에서 코루틴과 스레드는 다르다. 심지어 한 코루틴을 다른 스레드가 이어받을 수도 있다.

📌초기 설정

https://github.com/Kotlin/kotlinx.coroutines
코루틴 깃에 들어가 gradler과 android의 dependency를 복사해온다.

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"

문제 상황

간단한 예시를 보자

package com.anushka.coroutinesdemo1

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private var count = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnCount.setOnClickListener {
            tvCount.text = count++.toString()
        }
        btnDownloadUserData.setOnClickListener {
            downloadUserData()
        }
    }

    private fun downloadUserData() {
        for (i in 1..200000) {
            Log.i("MyTag", "Downloading user $i in ${Thread.currentThread().name}")
        }
    }
}

위와 같이 btnDownloadUserData를 누르고 btnCount를 누른 경우, 오래걸리는 프로세스가 실행되고 있어 btnCount를 연속적으로 눌러도 화면에 곧바로 count가 세지지 않다가 앞 프로세스가 끝난 다음에 나타난다.

📌코루틴 실행

🔑CoroutineScope

btnDownloadUserData.setOnClickListener {
            CoroutineScope(Dispatchers.IO).launch {
                downloadUserData()
            }
        }

앞선 코드의 btnDownloadUserData.setOnClickListener를 위와 같이 수정하였다. 코루틴 스코프를 열어 background에서 downloadUserData가 실행되도록 한다.

CoroutineScope는 코루틴의 범위를 설정해주는 인터페이스이다. GlobalScope이라고 하여 모든 생명주기에서 실행되는 top-level coroutine을 위한 인터페이스도 있다.

🔑Dispatcher

Dispatchers.IO는 context가 들어가는 곳이며 명시적으로 context를 선언하고 싶을 때 Dispatchers.IO + job 형태로 쓰기도 한다.
항상 코루틴은 메인 쓰레드에서 시작한다음 백그라운드 스레드로 스위칭 하는 것이 좋은데,

메인(UI) 스레드에서 코루틴을 선언할 때는
Dispatchers.Main을 사용,

백그라운드 스레드에서 공유 자원 작업을 할때(?)
Dispatchers.IO를 사용(local database 사용할 때, 네트워크 통신, 파일 가져오기 등)

CPU가 많이 드는 작업을 할 때는
Dispatchers.Default를 사용

메인 스레드에서 실행되지만 suspended 되거나 resumed 되면 suspending function이 있는 스레드에서 실행되게 할 때는
Dispactchers.Unconfined 사용

🔑lauch,async,produce, runBlocking

launch는 코루틴 빌더이다. 이외에도

async, produce, runBlocking 이 존재한다.

lauch는 현재 스레드를 막지 않고 새로운 코루틴을 실행한다. job instance 외에 리턴할 값이 없을 때 사용한다. 예를 들어 어떤 값을 넣고 계산할 때 사용할 수 없다.

async는 또한 스레드를 막지 않고 코루틴을 실행하는데, Deferred<T>(job instance를 상속받음) 이라고 하는 리턴값을 지정할 수 있다. await() 를 통하여 이 deferred 값을 받아온다.

Produce는 stream of elements를 생성하는 빌더이다. ReceiveChannel 인스턴스를 반환한다.

RunBlocking은 현재 실행되는 스레드를 막고 코루틴을 발생시킨다. 원하는 타입을 바로 반환한다.

📌코루틴 스위칭

class MainActivity : AppCompatActivity() {
    private var count = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnCount.setOnClickListener {
            tvCount.text = count++.toString()
        }
        btnDownloadUserData.setOnClickListener {
            CoroutineScope(Dispatchers.IO).launch {
                downloadUserData()
            }
        }
    }

    private suspend fun downloadUserData() {
        for (i in 1..200000) {
                tvUserMessage.text =  "Downloading user $i in ${Thread.currentThread().name}"
        }
    }
}

백그라운드 스레드에서 downloadUserData()를 실행시켜 화면에 바로바로 출력이 되게끔 하고 싶은데, view를 바꾸는 것은 오로지 메인 스레드에서만 가능하므로 스레드를 바꿔줘야 한다.

private suspend fun downloadUserData() {
        for (i in 1..200000) {
            withContext(Dispatchers.Main){
                tvUserMessage.text =  "Downloading user $i in ${Thread.currentThread().name}"
            }

        }
    }

downloadUserData()를 위와 같이 바꿔준다. withContext함수는 특정 구문을 다른 스레드에서 실행시킬 수 있도록 해준다.

📌Suspending Functions

withContext, withTimeOut, withTimeoutOrNull, join, delay, await, supervisorScope, coroutineScope 등의

suspending function은 일시중단을 의미하는데 suspend fun이 스레드를 막지 않고 suspend된 상태에서 다른 작업을 한다. 쉽게 말해

suspend fun MyFunction(){

Code block 1

Code block 2 //this one has a long running operation

Code block 3

Code block 4

}

이 있다면 block2가 실행될때까지 3,4가 실행되는 것을 멈춘다.

profile
컴퓨터와 교육 그사이 어딘가

0개의 댓글