GlobalScope 대 CoroutineScope 대 lifecycleScope
나는 AsyncTask
그것의 단순성 때문에 꽤 잘 이해하고 작업하는 데 익숙합니다 . 하지만 Coroutines
혼란 스럽습니다. 다음 각각의 차이점과 목적이 무엇인지 간단하게 설명해 주시겠습니까?
GlobalScope.launch(Dispatchers.IO) {}
GlobalScope.launch{}
CoroutineScope(Dispatchers.IO).launch{}
lifecycleScope.launch(Dispatchers.IO){}
lifecycleScope.launch{}
답변
먼저 정의부터 시작하여 명확하게하겠습니다. Coroutines 및 Coroutines Flow에 대한 튜토리얼 또는 플레이 그라운드가 필요한 경우 내가 만든 튜토리얼 / 플레이 그라운드를 확인할 수 있습니다 .
Scope
하나의 객체 만 포함하는 코 루틴을 시작하는 데 사용하는 객체입니다. CoroutineContext
public interface CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
public val coroutineContext: CoroutineContext
}
코 루틴 컨텍스트는 코 루틴이 실행되는 방식을 정의하는 일련의 규칙 및 구성입니다. 내부적으로는 가능한 키와 값 세트가있는 일종의 맵입니다.
코 루틴 컨텍스트는 변경할 수 없지만, 집합에 요소를 추가하여 새 컨텍스트 인스턴스를 생성하는 것처럼 플러스 연산자를 사용하여 컨텍스트에 요소를 추가 할 수 있습니다.
코 루틴의 동작을 정의하는 요소 집합은 다음과 같습니다.
- CoroutineDispatcher — 작업을 적절한 스레드로 디스패치합니다.
- 작업 — 코 루틴의 수명주기를 제어합니다.
- CoroutineName — 디버깅에 유용한 코 루틴의 이름입니다.
- CoroutineExceptionHandler — 포착되지 않은 예외 처리
디스패처 디스패처는 사용할 스레드 풀을 결정합니다. Dispatchers 클래스는 CoroutineContext에 추가 할 수있는 CoroutineContext 이기도합니다.
Dispatchers.Default : 큰 목록 정렬, 복잡한 계산 수행 등과 같은 CPU 집약적 인 작업. JVM의 공유 스레드 풀이이를 지원합니다.
Dispatchers.IO : 네트워킹 또는 파일 읽기 및 쓰기. 간단히 말해서 – 이름에서 알 수 있듯이 모든 입력 및 출력
Dispatchers.Main : Android의 기본 또는 UI 스레드에서 UI 관련 이벤트를 수행하기위한 필수 디스패처입니다.
예를 들어 RecyclerView에 목록 표시, 뷰 업데이트 등이 있습니다.
운영자에 대한 자세한 내용은 Android의 공식 문서 를 확인하세요 .
편집 공식 문서에 따르면Dispatchers.IO-이 디스패처는 기본 스레드 외부에서 디스크 또는 네트워크 I / O를 수행하도록 최적화되어 있습니다. 예에는 Room 구성 요소 사용, 파일 읽기 또는 쓰기, 네트워크 작업 실행이 포함됩니다.
Marko Topolnic의 답변
IO는 특별하고 유연한 스레드 풀에서 코 루틴을 실행합니다. 호출 스레드를 차단하는 IO API를 차단하는 레거시를 강제로 사용해야하는 경우에만 해결 방법으로 존재합니다.
둘 중 하나 일 수 있습니다.
Job 코 루틴 자체는 Job으로 표현됩니다. Job은 코 루틴에 대한 핸들입니다. 생성 한 모든 코 루틴 (시작 또는 비동기식)에 대해 코 루틴을 고유하게 식별하고 수명주기를 관리하는 Job 인스턴스를 반환합니다. 또한 Job을 CoroutineScope에 전달하여 수명주기에 대한 핸들을 유지할 수 있습니다.
코 루틴의 수명주기, 취소 및 부모-자식 관계를 담당합니다. 현재 작업은 현재 코 루틴의 컨텍스트에서 검색 할 수 있습니다. 작업은 신규, 활성, 완료 중, 완료 됨, 취소 중 및 취소됨과 같은 일련의 상태를 통과 할 수 있습니다. 상태 자체에 액세스 할 수는 없지만 작업 속성 인 isActive, isCancelled 및 isCompleted에 액세스 할 수 있습니다.
CoroutineScopeCoroutineContext
결합 된 CoroutineContext 주위에 래퍼를 만들기 위해 s를 인수로 취하는 간단한 팩토리 함수로 정의 됩니다.
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
ContextScope(if (context[Job] != null) context else context + Job())
internal class ContextScope(context: CoroutineContext) : CoroutineScope {
override val coroutineContext: CoroutineContext = context
// CoroutineScope is used intentionally for user-friendly representation
override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}
Job
제공된 컨텍스트에 아직 요소가없는 경우 요소를 생성합니다 .
GlobalScope 소스 코드를 살펴 보겠습니다.
/**
* A global [CoroutineScope] not bound to any job.
*
* Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
* and are not cancelled prematurely.
* Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
*
* Application code usually should use an application-defined [CoroutineScope]. Using
* [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
* on the instance of [GlobalScope] is highly discouraged.
*
* Usage of this interface may look like this:
*
* ```
* fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
* for (number in this) {
* send(Math.sqrt(number))
* }
* }
* ```
*/
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
보시다시피 확장됩니다 CoroutineScope
1- GlobalScope는 앱이 살아있는 한 살아 있습니다. 예를 들어이 범위에서 일부 계산을 수행하고 장치를 회전하면 작업 / 프로세스가 계속됩니다.
GlobalScope.launch(Dispatchers.IO) {}
앱이 살아있는 동안 실행되지만 사용으로 인해 IO 스레드에서 실행됩니다. Dispatchers.IO
2- 첫 번째와 동일하지만 기본적으로 컨텍스트가없는 경우 launch는 Dispatchers.Default를 사용하는 EmptyCoroutineContext를 사용하므로 차이점은 첫 번째 스레드와 만 다릅니다.
3- 이것은 구문 차이 만있는 첫 번째 것과 동일합니다.
4- lifecycleScope
은 LifeCycleOwner
해당 활동 또는 조각이 파괴 될 때 범위가 취소되는 Actvity 또는 Fragment의 lifCycle에 대한 확장 이며 바인딩됩니다.
/**
* [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
이것을 다음과 같이 사용할 수도 있습니다.
class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main + CoroutineName("🙄 Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable ->
println("🤬 Exception $throwable in context:$coroutineContext") } private val dataBinding by lazy { Activity3CoroutineLifecycleBinding.inflate(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(dataBinding.root) job = Job() dataBinding. button.setOnClickListener { // This scope lives as long as Application is alive GlobalScope.launch { for (i in 0..300) { println("🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
delay(300)
}
}
// This scope is canceled whenever this Activity's onDestroy method is called
launch {
for (i in 0..300) {
println("😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this") withContext(Dispatchers.Main) { dataBinding.tvResult.text = "😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this"
}
delay(300)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
세 축을 따라 목록을 구성하겠습니다.
GlobalScope
대CoroutineScope()
대lifecycleScope
Dispatchers.IO
상속 된 (암시 적) 디스패처 대- 디스패처를 범위 대 인수로 지정하십시오.
launch
1. 범위 선택
코 루틴에 대한 Kotlin의 큰 부분은 구조화 된 동시성 입니다. 즉, 모든 코 루틴이 종속성을 따르는 계층 구조로 구성됩니다. 일부 백그라운드 작업을 시작하는 경우 현재 "작업 단위"가 여전히 활성 상태 인 동안 결과가 나타날 것으로 예상한다고 가정합니다. 즉, 사용자가 작업 단위에서 벗어나지 않았고 더 이상 신경 쓰지 않습니다. 그 결과.
Android에서는 lifecycleScope
UI 활동에서 사용자의 탐색을 자동으로 따라가는 마음대로 사용할 수 있으므로 결과가 사용자에게 표시되는 백그라운드 작업의 부모로 사용해야합니다.
최종적으로 완료해야하지만 사용자는 결과를 기다리지 않는 작업이있을 수도 있습니다. 이를 위해서는 WorkManager
사용자가 다른 애플리케이션으로 전환하더라도 안전하게 계속 진행할 수있는 Android 또는 유사한 기능을 사용해야 합니다. 일반적으로 로컬 상태를 서버 측에 보관 된 상태와 동기화하는 작업입니다.
이 그림에서 GlobalScope
기본적으로 구조화 된 동시성에서 탈출구입니다. 범위를 제공하는 형식을 만족시킬 수 있지만 구현해야하는 모든 메커니즘을 무효화합니다. GlobalScope
취소 할 수 없으며 부모가 없습니다.
CoroutineScope(...).launch
즉시 잊어 버리고 취소 할 방법이없는 부모없이 범위 개체를 만들기 때문에 쓰기 가 잘못되었습니다. 사용하는 것과 비슷 GlobalScope
하지만 훨씬 더 해키합니다.
2. 발송자 선택
코 루틴 디스패처는 코 루틴을 실행할 수있는 스레드를 결정합니다. Android에는 다음 세 가지 운영자가 있습니다.
Main
단일 GUI 스레드에서 모든 것을 실행합니다. 그것은 당신의 주요 선택이어야합니다.IO
특별하고 유연한 스레드 풀에서 코 루틴을 실행합니다. 호출 스레드를 차단하는 IO API를 차단하는 레거시를 강제로 사용해야하는 경우에만 해결 방법으로 존재합니다.Default
또한 스레드 풀을 사용하지만 CPU 코어 수와 같은 고정 크기입니다. GUI에서 글리치를 유발하는 데 충분히 오래 걸리는 계산 집약적 인 작업 (예 : 이미지 압축 / 압축 해제)에 사용합니다.
3. 디스패처 지정 위치
먼저 사용중인 코 루틴 범위에 지정된 디스패처를 알고 있어야합니다. GlobalScope
아무 것도 지정하지 않으므로 일반 기본값 인 Default
디스패처가 적용됩니다. 디스패처를 lifecycleScope
지정합니다 Main
.
CoroutineScope
생성자를 사용하여 임시 범위를 만들면 안된다고 이미 설명 했으므로 명시 적 디스패처를 지정하는 적절한 위치는 launch
.
당신이 쓸 때 기술적 인 세부에서 someScope.launch(someDispatcher)
의 someDispatcher
인수는 실제로 하나의 요소, 발송자가 일어나는 본격적인 코 루틴 컨텍스트 개체입니다. 실행중인 코 루틴은 코 루틴 범위의 코 루틴과 매개 변수로 제공 한 코 루틴을 결합하여 자체적으로 새로운 컨텍스트를 생성합니다. 그 위에, 그것은 Job
그 자체 로 신선한 것을 창조하고 그것을 문맥에 추가합니다. 작업은 컨텍스트에서 상속 된 작업의 자식입니다.
TL; DR
GlobalScope.launch (Dispatchers.IO) : .NET에서 최상위 코 루틴을 시작
Dispatchers.IO
합니다. 코 루틴은 바인딩 해제되어 완료되거나 취소 될 때까지 계속 실행됩니다. 프로그래머가join()
또는에 대한 참조를 유지해야하기 때문에 종종 권장되지 않습니다cancel()
.GlobalScope.launch 위와 동일하지만 :
GlobalScope
용도Dispatchers.Default
지정되지 않은 경우. 종종 낙담합니다.CoroutineScope (Dispatchers.IO) .launch :
Dispatchers.IO
코 루틴 빌더에 디스패처가 지정되지 않은 경우 사용하는 코 루틴 범위를 생성합니다.launch
CoroutineScope (Dispatchers.IO) .launch (Dispatchers.Main) : 보너스 1입니다. 같은 코 루틴의 위와 범위 (범위 인스턴스가있는 경우 같은!)하지만 재정의 사용
Dispatcher.IO
과Dispatchers.Main
이 코 루틴에 대한합니다.lifecycleScope.launch (Dispatchers.IO) : AndroidX에서 제공하는 lifecycleScope 내에서 코 루틴을 시작합니다. 코 루틴은 라이프 사이클이 무효화 되 자마자 취소됩니다 (예 : 사용자가 프래그먼트에서 벗어나면).
Dispatchers.IO
스레드 풀로 사용 합니다.lifecycleScope.launch : 위와 동일하지만
Dispatchers.Main
지정하지 않으면 사용합니다.
외식
코 루틴 범위 는 구조화 된 동시성을 촉진 하여 동일한 범위에서 여러 코 루틴을 시작하고 필요한 경우 범위를 취소 할 수 있습니다 (그러면 해당 범위 내의 모든 코 루틴이 취소됨). 반면에, GlobalScope의 코 루틴은에 순서의 참조를 유지할 필요가 스레드에 가깝다 join()
또는 cancel()
그것. 다음 은 Medium에 대한 Roman Elizarov 의 훌륭한 기사 입니다.
CoroutineDispatcher 는 launch {}
사용할 스레드 풀에 대해 코 루틴 빌더 (우리의 경우 ) 에 알려줍니다 . 사용 가능한 몇 가지 사전 정의 된 Dispatcher가 있습니다.
Dispatchers.Default
-CPU 코어 수에 해당하는 스레드 풀을 사용합니다. CPU 바운드 워크로드에 사용해야합니다.Dispatchers.IO
-64 개의 스레드 풀을 사용합니다. 스레드가 일반적으로 대기중인 IO 바운드 워크로드에 이상적입니다. 네트워크 요청 또는 디스크 읽기 / 쓰기를위한 것일 수 있습니다.Dispatchers.Main
(Android 만 해당) : 메인 스레드를 사용하여 코 루틴을 실행합니다. UI 요소 업데이트에 이상적입니다.
예
위의 6 가지 시나리오에 해당하는 6 가지 기능으로 작은 데모 조각을 작성했습니다. Android 장치에서 아래 조각을 실행하면; 조각을 연 다음 조각을 그대로 둡니다. GlobalScope 코 루틴 만 여전히 살아 있다는 것을 알 수 있습니다. 라이프 사이클 코 루틴은 라이프 사이클이 유효하지 않은 경우 lifecycleScope에 의해 취소됩니다. 반면에 CoroutineScope는 onPause()
우리가 명시 적으로 수행 한 호출시 취소됩니다 .
class DemoFragment : Fragment() {
private val coroutineScope = CoroutineScope(Dispatchers.IO)
init {
printGlobalScopeWithIO()
printGlobalScope()
printCoroutineScope()
printCoroutineScopeWithMain()
printLifecycleScope()
printLifecycleScopeWithIO()
}
override fun onPause() {
super.onPause()
coroutineScope.cancel()
}
private fun printGlobalScopeWithIO() = GlobalScope.launch(Dispatchers.IO) {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[GlobalScope-IO] I'm alive on thread ${Thread.currentThread().name}!") } } private fun printGlobalScope() = GlobalScope.launch { while (isActive) { delay(1000) Log.d("CoroutineDemo", "[GlobalScope] I'm alive on ${Thread.currentThread().name}!")
}
}
private fun printCoroutineScope() = coroutineScope.launch {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[CoroutineScope] I'm alive on ${Thread.currentThread().name}!") } Log.d("CoroutineDemo", "[CoroutineScope] I'm exiting!") } private fun printCoroutineScopeWithMain() = coroutineScope.launch(Dispatchers.Main) { while (isActive) { delay(1000) Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm alive on ${Thread.currentThread().name}!")
}
Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm exiting!")
}
private fun printLifecycleScopeWithIO() = lifecycleScope.launch(Dispatchers.IO) {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[LifecycleScope-IO] I'm alive on ${Thread.currentThread().name}!") } Log.d("CoroutineDemo", "[LifecycleScope-IO] I'm exiting!") } private fun printLifecycleScope() = lifecycleScope.launch { while (isActive) { delay(1000) Log.d("CoroutineDemo", "[LifecycleScope] I'm alive on ${Thread.currentThread().name}!")
}
Log.d("CoroutineDemo", "[LifecycleScope] I'm exiting!")
}
}
suspend
함수 를 시작 하려면 CoroutineScope
. 모든이 CoroutineScope
있습니다 CoroutineContext
. (적절한 스레드로 작업 디스패치), (코 루틴의 수명주기를 제어), (잡히지 않은 예외 처리), (디버깅에 유용한 코 루틴 이름 )을 CoroutineContext
포함 할 수있는 맵은 어디에 있습니까 ?Dispatcher
Job
CoroutineExceptionHandler
CoroutineName
GlobalScope.launch(Dispatchers.IO) {}
-GlobalScope.launch
전역 코 루틴을 만들고 취소해서는 안되는 작업에 사용하지만 더 나은 대안은 Application 클래스에서 사용자 지정 범위를 만들고이를 필요한 클래스에 삽입하는 것입니다. 이것은 테스트를 위해 를 사용CoroutineExceptionHandler
하거나 교체 할 수있는 장점이 있습니다CoroutineDispatcher
.GlobalScope.launch{}
-와 동일GlobalScope.launch(Dispatchers.IO) {}
하지만에서 실행coroutines
됩니다Dispatchers.Default
. 컨텍스트에 디스패처가 지정되지 않은 경우 사용되는Dispatchers.Default
기본값Dispatcher
입니다.CoroutineScope(Dispatchers.IO).launch{}
-하나의 매개 변수로 범위를 만들고 스레드coroutine
에서 새로 시작IO
합니다. 시작된 개체와 함께 파괴됩니다. 하지만 당신은 수동으로 호출해야합니다.cancel()
위해CoroutineScope
제대로 작업을 종료합니다.lifecycleScope.launch(Dispatchers.IO){}
-Lifecycle
또는LifecycleOwner
(Activity
또는Fragment
) 에서 사용할 수있는 기존 범위 이며 종속성이있는 프로젝트에 제공됩니다androidx.lifecycle:lifecycle-runtime-ktx:*
. 그것을 사용하면 수동 생성에서 제거 할 수 있습니다CoroutineScope
.Dispatchers.IO
을 차단하지 않고 에서 작업을 실행MainThread
하고 작업lifecycle
이 파괴 되면 작업이 취소되는지 확인합니다 .lifecycleScope.launch{}
- 동일하게lifecycleScope.launch(Dispatchers.IO){}
그 만드는CoroutinesScope
기본으로 당신을위한Dispatchers.Main
매개 변수 및 실행coroutines
에Dispatcher.Main
당신이 작업 할 수 있습니다 의미합니다UI
.