관리 메뉴

오늘도 더 나은 코드를 작성하였습니까?

13장 코루틴 스코프 만들기 본문

Coroutine/코틀린 코루틴

13장 코루틴 스코프 만들기

hik14 2026. 3. 22. 18:02

CoroutineScope 팩토리 함수

CoroutineScope는 coroutineContext를 유일한 프로퍼티로 가지고 있는 인터페이스.

 

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

 

CoroutineScope 인터페이스를 직접 구현한 클래스를 사용하지 않는다 대부분...

-> cancel, ensureActice 같은 다른 CoroutineScope의 메소드를 직접 호출하면 문제가 생길수있다.

 

CoroutineScope 팩토리 함수를 이용한다.

컨텍스트를 넘겨 받아 스코프를 만든다.

public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

 

안드로이드에서 스코프 만들기

사용자에게 보여주는 부분을 ViewModel, Presenters와 같은 객체로 추출한다.

일반적으로 코루틴이 가장 시작되는 부분이다.

Usecase, Repository는 다른계층에서 보통 중단함수를 사용한다.

abstract class BaseViewModel : ViewModel(){
    protected val scope = CoroutineScope(Dispatchers.Main)
}

class MainViewModel(
    private val userRepo: UserRepository,
    private val newsRepo: UserRepository,
) : BaseViewModel {
    
	init{
        scope.launch {
            val user = userRepo.getUser()
            view.showUserData(user)
        }
        scope.launch {
            val news = newsRepo.getNews()
                .sortedByDescending{ it.date }
            view.showNews(news)
        }
    }
}

 

안드로이드에서는 메인 스레드가 많은 수의 함수를 호출해야 하기 때문에 기본 디스패처를 Dispatcher.Main으로 정하는것이 좋다.

 

사용자가 Screen을 떠나면 ViewModel에서 진행중인 모든 작업을 취소한다.

onCleared에서 스코프를 취소한다.

abstract class BaseViewModel : ViewModel(){
    protected val scope = CoroutineScope(Dispatchers.Main + Job())
    
    override fun onCleared() {
        scope.cancel()
    }
}

 

이때 스코프 전체를 취소하는것이 아니라. 자식 코루틴만 취소하는것이 더 좋은 방법이다.
자식 코루틴만 취소하면 ViewModel이 액티브한 상태로 유지되면, 새로운 코루틴을 시작할 수 있다.

abstract class BaseViewModel : ViewModel(){
    protected val scope = CoroutineScope(Dispatchers.Main + Job())

    override fun onCleared() {
        scope.coroutineContext.cancelChildren()
    }
}

 

* job을 사용하고 에러가 발생하여 자식 코루틴 하나가 취소된 경우 부모와 다른 자식 코루틴 모두가 함께 취소된다 (구조적 동시성)

코루틴이 독립적 작동을 원하면, job 대신 SupervisorJob을 사용한다.

abstract class BaseViewModel : ViewModel(){
    protected val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())

    override fun onCleared() {
        scope.coroutineContext.cancelChildren()
    }
}

 

잡히지 않는 예외를 처리하는 기본적 방법

abstract class BaseViewModel : ViewModel(){
    
    private val _failure = MutableStateFlow<Throwable?>(null)
    val failure = _failure.asStateFlow()

    private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
        _failure.value = throwable
    }
    
    private val context = Dispatchers.Main + SupervisorJob() + exceptionHandler
    
    protected val scope = CoroutineScope(context)

    override fun onCleared() {
        scope.coroutineContext.cancelChildren()
    }
}

viewModelScope와 lifecycleScope

안드로이드에서는 스코프를 따로 정의하는 대신에 viewModelScope와 lifecycleScope를 제공한다.

Dispatcher.Main / SupervisorJob을 사용하고, 뷰모델 및 라이프 사이클이 종료되었을때 잡을 취소 시킨다.

 

스코프에서 특정 컨텍스트(CoroutineExceptionHandler)가 필요 없다면 viewModelScope lifecycleScope를 사용하는것이 편리하고 좋다.

 

open class BaseViewModel : ViewModel() {
    protected val commonHandler = CoroutineExceptionHandler { _, t ->
        // 공통 에러 처리 로직
    }
}

class UserViewModel : BaseViewModel() {
    fun load() {
        viewModelScope.launch(commonHandler) { ... }
    }
}

 

백앤드에서 코루틴 만들기

백앤드에선, 따로 스코프를 만들 필요는 거의 없다.

 

- 스레드 풀을 가진 커스텀 디스패처

- 각각의 코루틴을 독립적으로 만들어 주는 SupervisorJob

- 적절한 에러 코드에 응답하고, 데르 래터를 보내거나, 발생한 문제에 대해 로그를 남기는 CoroutineExceptionHandler.

@Configuration
public class CoroutineConfiguration {
    @Bean(name = "coroutineDispatcher")
    fun coroutineDispatcher(): CoroutineDispatcher =
        Dispatchers.IO.limitedParallelism(5)

    @Bean(name = "coroutineExceptionHandler")
    fun coroutineExceptionHandler() = CoroutineExceptionHandler { context, throwable -> 
        FirebaseCrashlytics.getInstance().recordException(throwable)
    }

    @Bean
    fun coroutineScope(
        coroutineDispatcher: CoroutineDispatcher,
        coroutineExceptionHandler: CoroutineExceptionHandler
    ): CoroutineScope(
        SupervisorJob() +
        coroutineDispatcher +
        coroutineExceptionHandler
    )
}

 

추가적인 호출을 위한 스코프 만들기

 

추가적인 연산을 시작하기 위한 스코프를 종종 만든다.

이런 스코프는 함수나 생성자의 인자를 통해 주로 주입된다.

스코프 호출을 중단하기 위한 목적으로만 사용하려는 경우 SupervisorScope를 사용하는 것만으로 충분합니다.

private val exceptionHandler =
    CoroutineExceptionHandler { _, throwable ->
        FirebaseCrashlytics.getInstance().recordException(throwable)
    }

val analyticsScope = CoroutineScope(SupervisorJob() + exceptionHandler)

'Coroutine > 코틀린 코루틴' 카테고리의 다른 글

12장 디스패처2  (0) 2026.03.15
12장 디스패처1  (0) 2026.03.03
11장 코루틴 스코프 함수 - 2  (0) 2026.02.21
11장 코루틴 스코프 함수 - 1  (1) 2026.02.18
10장 예외처리  (0) 2026.02.09