안드로이드/Android 비동기 처리

Coroutine suspend function

코딩하는후운 2022. 4. 19. 19:39
반응형

코루틴은 일시중단이 가능하다.
launch로 실행하든 async로 실행하든 내부에 해당 코루틴을 일시 중단 해야하는 동작이 있으면 코루틴은 일시 중단 된다.

# suspend

사전적 의미로는 '중지하다'라는 뜻이다.

coroutine에서는 시작하고, 멈추고, 다시 시작할 수 있는 함수라고 한다.

  • suspend란 비동기 실행을 위한 중단 지점의 의미
  • 즉, 잠시 중단(suspend)한다는 의미이고, 잠시 중단 후에 다시 시작(resume) 된다는 뜻

# Suspend function이 없다면?

하나의 thread가 block될 경우, 해당 thread는 다른 작업을 할 수 없는 block상태에 놓이게 된다.

즉, blocked 상태가 끝날 때까지 해당 thread는 중지 상태인 것.

 

하지만, suspend function을 사용한다면 blocked된 상태에 놓일 때, 그 작업을 supend하고 그 기간 동안 thread에서 다른 작업을 수행할 수가 있다. (자원 효율)

 

# 기본 순차 실행 (Sequential by Default)

기본적으로 Code & Function은 순차적(Sequential)으로 수행

 

일정시간 delay 후 Integer값을 반환하는 두개의 suspend(중단) 함수 생성

결과에서 총 수행시간을 보면 함수가 순차적으로 수행되었기 때문에 3016ms가 걸렸습니다.

먼저 testOne()이 1000ms대기 후 완료되고, 그 다음 testTwo()가 2000ms를 Delay하기 때문에 그렇습니다

 

# async 통한 동시 수행 (Concurrent using async)

위의 두 중단함수가 서로 인과관계(의존성)을 갖지 않는다면, 두 개의 함수를 동시에 수행시 더 빠른 동작을 구현 가능

 

launch{}async{}는 동일한 컨셉이지만, return 객체의 차이가 있는 코루틴 빌더입니다.

launch{} -> Job return
async{} -> Deferred<T> return

 

Job은 launch로 생성된 코루틴의 상태를 관리하는 용도로 사용하고 결과값을 리턴받을 수 없으나,

Deferred는 async블럭 내 수행된 결과를 원하는 시점에 return받을 수 있다는 큰 차이가 있습니다.

Deferred는 job을 상속받아 구현되었기 때문에 Job의 기능을 사용할 수 있습니다.

 

두 개의 작업이 동시에 실행되었기 때문에 testTwo()의 delay(2000)을 마지막으로 모든 실행이 종료된 걸 확인 가능

 

# suspend function을 사용하는 경우와 하지 않을 때 thread 자원 사용 확인

  • suspend 함수에서는 thread가 blocked되지 않고, suspend된 상태에서 다른 작업을 수행하는지?
  • blocked된 작업이 suspend되고 다시 resume되는지?
  • delay() 자체는 suspend 함수로 coroutineScope 에서만 사용 가능
  • Thread.sleep() 은 suspend 함수가 아님.

 

1. suspend function 사용하지 않은 테스트 코드

2. suspend function 사용 테스트 코드

 

1. suspend function을 사용하지 않은 경우 결과

각 함수 Task1() 와 Task2() 는 각기 다른 thread 에서 수행되었다.

  • Task1() : DefaultDispatcher-worker-2
  • Task2() : DefaultDispatcher-worker-1

이유: Thread.sleep() 동안 thread가 blocked되어 다른 작업을 수행할 수 없기 때문에, 서로 다른 스레드에서 실행 된다.

각 thread 의 작업 플로우

2. suspend function을 사용한 경우

각 함수 Task1() 와 Task2() 는 하나의 동일한 thread(DefaultDispatcher-worker-1) 에서 수행되었다.

이유 : delay()동안 thread가 suspend되어 다른 함수의 작업을 수행할 수 있기 때문이다.

(suspend를 사용한다고 무조건 하나의 스레드에서만 실행되는 것은 아니다.)
여기서는 2개의 함수의 작업이 서로 다른 시간에 일어나기 때문에 가능!

각 thread 의 작업 플로우

 

# suspend function이 suspend된 후 resume할 수 있는 이유

  • 함수에 suspend키워드를 추가하면 => 바이트코드를 보면 함수의 파라미터에 "Continuation"이 추가됨.

  • 이 Continuation이 프로그램의 현재 상태를 저장한다. Continuation wrapper 내에 나머지 코드를 전달하는 것처럼 생각할 수 있다.

위의 suspendTask()함수의 디컴파일된 코드를 보면 함수 파라미터로 Continuation 객체가 들어가 있는 것을 확인할 수 있다.

1. Continuation 인터페이스

2. 실제 suspend함수가 Continuation파라미터가 추가되고 completion.resume에 값을 넣는 모습

3. 해당 소스에 Label로 상태가 저장/관리 되는듯(나중에 다시 suspend되었다가 resume시에 라벨로 실행하기 위해(?))

4. 라벨에 따른 소스 실행

5. 내부 동작은 실제 함수의 StateMachine이름의 CoroutineImpl(=Continuation)을 리턴하는 클래스가 만들어지는 듯(?)

라벨0 : user객체

라벨1 : userDb객체

라벨2 : result객체 리턴

6. StateMachine에 의해 continuation을 가져오고 해당 라벨 실행

 

# Continuation는 누가 전달해 줄까?

보통 CoroutineScope의 launch 함수 내에서 suspend를 호출하게 됩니다.

launch 내부 코드

StandaloneCoroutine class는 아래와 같이 구현되어 있습니다.

이 중 AbstractCoroutine<> 클래스 정보가 보입니다.

AbstractCoroutine 클래스는 아래와 같습니다.

여기에 보면 Continuation<>을 상속받고 있음을 확인할 수 있습니다.

결국 launch 내에서 suspend 함수를 부르면 AbstractCoroutine에서 상속받고 있는 Continuation이 있기에 이를 활용해 suspend 함수가 구현되어 있음을 알 수 있습니다.
Continuation는 interface로 정의되어 있는데, resumeWith() 함수를 포함하고 있습니다.
resumeWith 함수를 통해 suspend 함수의 작업이 완료되었을 경우 resumeWith()으로 복귀시키게 됩니다.

 

# 코루틴 suspend function 추가 종류(?)

withTimeout : 코루틴이 정해진 시간 안에 실행되지 않으면 예외를 발생시킨다.
withTimeoutOrNull : 정해진 시간 안에 실행되지 않으면 null을 반환한다.
awaitAll : 모든 작업의 성공을 기다리면서 작업중에 하나라도 실패하면 awaitAll 또한 실패한다.
joinAll : 모든 작업이 끝날 때 까지 현재 작업을 일시 중단시킨다.

 

결론

  • coroutine suspend 함수는 thread 를 block 하지 않는다.
  • 따라서, 하나의 thread 에서 여러 개의 coroutine 을 실행할 수 있다.
    특정 작업이 suspend 되고 resume 될 때까지 이 사이에 다른 작업을 수행할 수 있기 때문.
  • 그래서 suspend 는 많은 concurrent 작업을 지원하면서 blocking 에 대한 메모리를 절약할 수 있다.

 

 

참조 :

https://nuritech.tistory.com/16#suspend_%EB%8A%94_%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80._

https://jaejong.tistory.com/63

https://thdev.tech/coroutines/2021/07/22/Coroutine-suspend/

https://velog.io/@rhkswls98/Android-Kotlin-Coroutine-%EC%A0%95%EB%A6%AC

반응형