본문 바로가기
안드로이드/코틀린

코틀린 스레드 및 코루틴 중복 실행 방지 방법

by 시작이반의반 2024. 5. 14.

스레드 코틀린 중복 실행 방지
비동기 카운트 동작 중복 실행 로그

앱에서 비동기 작업을 처리할 때는 중복 실행으로 인한 불필요한 리소스 사용이나,

예상치 못한 오류로 인한 비정상 종료 등의 문제가 발생할 수 있습니다.

이를 방지하기 위해 중복 실행으로 인한 문제여부를 체크하고 중복 실행에 대한 작업을 추가하는 것은

앱의 안정성을 높일 수 있는 방법 중 하나입니다.

따라서, 스레드 및 코루틴을 이용한 비동기 작업을 처리할 때 중복 실행을 관리하는 방법에 대해 정리해보려고 합니다.

 

 

 

 

 

스레드(Thread) 중복 실행 방지 : Flag

 

"isThreadRunning"이라는 플래그를 사용해서 연속적으로 호출되더라도 중복해서 실행되는 것을 막을 수 있습니다.

private var isThreadRunning = false
private fun threadExample() {
    if (isThreadRunning) { // 중복 실행 방지
        println("Thread is already running.")
        return
    }

    val thread = Thread {
        isThreadRunning = true

        var count = 0
        while (count <= 5) {
            println("currentThread(${Thread.currentThread().name}), count $count")
            count++
            Thread.sleep(1000)
        }

        isThreadRunning = false
    }
    thread.start()
}

 

비동기 작업에서 플래그 사용 시 주의사항!!!

비동기 작업 특성상 여러 스레드에서 서로 다른 시간에 실행되기 때문에
플래그를 올바르게 관리하지 않으면 예상치 못한 결과가 발생할 수 있습니다.
(ex. 여러 스레드에서 동시에 플래그에 접근하거나 변경하는 경우)

 

 

 

스레드(Thread) 중복 실행 방지 : Executors.newSingleThreadExecutor

 

Executors.newSingleThreadExecutor를 사용하면 중복 실행을 관리하는데 이점들이 있습니다.

Executors.newSingleThreadExecutor의 특징

  • 스케줄링한 작업은 큐에 쌓이고 하나의 스레드에서 순차적으로 작업을 처리한다.
  • 스케줄링은 execute 또는 submit 사용해서 작업을 큐에 쌓는다.
  • submit은 스케줄링한 작업의 결과를 확인할 수 있는 Future를 반환하고,
    execute는 작업의 결과를 기다리지 않고 즉시 반환한다.

 

아래 예시처럼 Executors.newSingleThreadExecutor를 사용하면 단일 스레드 특성상 연속적으로 호출되더라도

하나의 스레드에서 작업이 순차적으로 처리되는 게 보장됩니다.

private val singleThreadExecutor = Executors.newSingleThreadExecutor()
private fun executorExample() {
    // 연속적으로 호출이되도 큐에 쌓인 작업이 하나의 스레드에서 순차적으로 처리 (중복 실행 X)
    singleThreadExecutor.execute {
        var count = 0
        while (count <= 5) {
            println("currentThread(${Thread.currentThread().name}), count $count")
            count++
            Thread.sleep(1000)
        }
    }
}

 

 

또한, submit을 통한 스케줄링은 작업의 결과를 확인할 수 있는 Future를 반환해 주기 때문에

작업 중에 큐에 추가되는 걸 원치 않는다면 아래와 같이 처리도 가능합니다.

private val singleThreadExecutor = Executors.newSingleThreadExecutor()
private var future: Future<*>? = null
private fun executorExample() {
    if (future?.isDone == false) { // 작업 중에는 큐에 추가되지 않도록 처리
        println("Thread is already running.")
        return
    }

    future = singleThreadExecutor.submit {
        var count = 0
        while (count <= 5) {
            println("currentThread(${Thread.currentThread().name}), count $count")
            count++
            Thread.sleep(1000)
        }
    }
}

 

 

 

 

 

코루틴(Coroutine) 중복 실행 방지 : Job, suspend

 

코틀린에서 비동기 작업으로 코루틴을 빼면 서운할 수 있습니다.

코루틴 특징

  • launch 또는 async를 사용해서 비동기적으로 실행할 코드 블록을 정의한다.
  • launch는 비동기 작업을 관리할 수 있는 Job 객체를 반환한다.
  • async는 비동기 작업을 관리할 수 있는 Deferred 객체를 반환한다.
  • async는 suspendDeferred.await를 사용해서 비동기 작업을 순차적으로 처리할 수 있다.

 

launch는 비동기 작업을 관리할 수 있는 Job 객체를 반환하며 해당 객체를 통해서 중복 실행 방지를 할 수 있습니다.

private val coroutineScope = CoroutineScope(Dispatchers.Default)
private var job: Job? = null
private suspend fun coroutineExample() {
    if (job?.isActive == true) { // 중복 실행 방지
        println("Coroutine is already running.")
        return
    }

    job = coroutineScope.launch {
        var count = 0
        while (count <= 5) {
            println("currentThread(${Thread.currentThread().name}), count $count")
            count++
            delay(1000)
        }
    }
}

 

 

마찬가지로 async를 사용했을 때 Deferred 객체를 반환받고 중복 실행에 대한 처리가 가능합니다.

private val coroutineScope = CoroutineScope(Dispatchers.Default)
private var deferred: Deferred<Boolean>? = null
private fun coroutineExample() {
    if (deferred?.isActive == true) { // 중복 실행 방지
        println("Thread is already running.")
        return
    }

    deferred = coroutineScope.async {
        var count = 0
        while (count <= 5) {
            println("currentThread(${Thread.currentThread().name}), count $count")
            count++
            delay(1000)
        }
        true // 비동기 작업이 완료에 대한 결과값 true 반환
    }
}

 

 

코루틴에서 suspend와 await를 함께 사용하면 연속적으로 호출해도 해당 비동기 작업은 순차적으로 처리되기 때문에

중복 실행에 대한 오류를 피할 수 있습니다.

private val coroutineScope = CoroutineScope(Dispatchers.Default)
private var deferred: Deferred<Boolean>? = null
private suspend fun coroutineExample(): Boolean? {
    deferred = coroutineScope.async {
        var count = 0
        while (count <= 5) {
            println("currentThread(${Thread.currentThread().name}), count $count")
            count++
            delay(1000)
        }
        true // 비동기 작업 완료에 대한 결과값 true 반환
    }

    return deferred?.await() // 비동기 작업이 끝날 때까지 대기하고 결과 값 반환
}

 

댓글