코루틴

소정·2023년 6월 5일
0

Kotlin

목록 보기
23/27

멀티스레드

멀티스레드는 CPU를 조금씩 나눠쓰는 모양임
스레드가 요리사라면 멀티 스레드는 여러 요리사가 화구 (CPU)를 번갈아 쓰는 구조 그래서 다른 요리사가 사용중에는 기존 요리사는 동작을 멈춘다

코루틴

코루틴은 하나의 요리사(스레드)가 파스타를 만들면서 스테이크까지 굽는 형식, 즉 팬이 두개
자리를 비켜가며 멈추는 행동이 없어서 조금 더 빠르게 동시 작업이 가능하다

  • 경량 스레드
  • 코틀린의 언어적 특성 중 하나
  • 스레드가 작업을 하나당 여러개씩 한다
  • 스레드 작업을 결정하는 것은 잡스케줄러가 알아서 관리함
  • 스레드를 멈추지 않고 비동기처리하는 것

코루틴을 구동하는 2개의 범위(Scope)가 존재

[1] GlobalScope

  • 앱 전체의 생명주기와 함꼐 관리되는 범위
  • 앱이 끝날때 까지 처리할 일이 있을 때 사용하는 일을 할 대 쓴다

package com.bsj0420.ex100coroutinetest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.bsj0420.ex100coroutinetest.databinding.ActivityMainBinding
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

    val binding :ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        // * 코루틴 - 경량 스레드
        // 스레드를 멈추지 않고 비동기처리하는 것
        // 하나의 스레드 안에 여러개의 코루틴 실행

        //스레드가 요리사라면 멀티 스레드는 여러 요리사가 화구 (CPU)를 번갈아 쓰는 구조
        //그래서 다른 요리사가 사용중에는 기존 요리사는 동작을 멈춘다

        //코루틴은 하나의 요리사(스레드)가 파스타를 만들면서 스테이크까지 굽는 형식, 즉 팬이 두개
        //자리를 비켜가며 멈추는 행동이 없어서 조금 더 빠르게 동시 작업이 가능하다

        // 프로세스 > 스레드 > 코루틴

        //코루틴을 구동하는 2개의 범위(Scope)가 존재
        //1. GlobalScope : 앱 전체의 생명주기와 함꼐 관리되는 범위
        // 앱이 끝날떄 까지 처리할 일이 있을 때 사용하는 일을 할 대 쓴다
        //2. CoroutineScope : 버튼 클릭 같은 특정이벤트 순간에 해야할 Job을 위해 실행되는 범위
        // ex) 네트워크 통신 , DB CRUD (Room) 특정연산 수행 등...

        //실습 1) GlobalScope 코드 연습
        binding.btn.setOnClickListener {
            //코루틴 없이 오래걸리는 작업 실행
            for ( n in 0..9 ) {
                Log.d("TAG", "n : $n")

                Thread.sleep(500)
            }

            Toast.makeText(this, "aaa", Toast.LENGTH_SHORT).show()
        }

        //비동기 적업으로 위 작업을 수행 - 코루틴 사용하여...
        binding.btn2.setOnClickListener {

            GlobalScope.launch {
                for ( n in 0..9 ) {
                    Log.d("TAG", "n : $n - ${Thread.currentThread().name}")
                    delay(500)
                }
            }

            Toast.makeText(this, "aaa", Toast.LENGTH_SHORT).show()
        }

    }
}

[2] CoroutineScope

  • 버튼 클릭 같은 특정이벤트 순간에 해야할 Job을 위해 실행되는 범위
    ex) 네트워크 통신 , DB CRUD (Room) 특정연산 수행 등...
  • CoroutineScope는 GlobalScope와 다르게 해당 작업을 어떤 스레드에게 보낼지 결정하는 Dispatcher을 지정해야됨

Dispatcher의 종류

1) Dispatchers.Default

기본 스레드풀의 스레드를 사용 (CPU 작업이 많은 연산작업에 적함)

2) Dispatchers.IO

DB나 네트워크 IO 스레드 사용 (파일 입출력, 서버작업에 적합) ****

3) Dispatchers.Main

Main 스레드 사용 (UI 작업등에 적합)

4) Dispatchers.Unconfined

조금 특별한 디스패처 (해당 코루틴을 호출하는 스레드에서 실행)

package com.bsj0420.ex100coroutinetest

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.bsj0420.ex100coroutinetest.databinding.ActivityMainBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URL

class MainActivity : AppCompatActivity() {

    val binding :ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        //실습 2) CoroutineScope 코드 연습
        //CoroutineScope는 GlobalScope와 다르게 해당 작업을 어떤 스레드에게 보낼지 결정하는 Dispatcher을 지정해야됨

        //Dispatcher의 종류
        //1) Dispatchers.Default - 기본 스레드풀의 스레드를 사용 (CPU 작업이 많은 연산작업에 적함)
        //2) Dispatchers.IO - DB나 네트워크 IO 스레드 사용 (파일 입출력, 서버작업에 적합) ****
        //3) Dispatchers.Main - Main 스레드 사용 (UI 작업등에 적합)
        //4) Dispatchers.Unconfined - 조금 특별한 디스패처 (해당 코루틴을 호출하는 스레드에서 실행)

        //Dispatchers.Default
        binding.btn3.setOnClickListener {
            //Dispatchers.Default 사용
            CoroutineScope(Dispatchers.Default).launch {
                for (n in 100..110) {
                    Log.d("TAG","n : $n - ${Thread.currentThread().name}")

                    //UI 작업은 main 스레드만 가능
                    binding.tv.text = "n : $n"

                    delay(500)
                }
            }

            Toast.makeText(this, "bbb", Toast.LENGTH_SHORT).show()
        }


        //Dispatchers.Main
        binding.btn4.setOnClickListener {
            CoroutineScope(Dispatchers.Main).launch {
                for (n in 200..210) {
                    Log.d("TAG","n : $n - ${Thread.currentThread().name}")

                    //체크 필요 - 원래는 UI 변경 불가함,,,
                    binding.tv.text = "n : $n"

                    delay(500)
                }
            }

            Toast.makeText(this, "main", Toast.LENGTH_SHORT).show()
        }

        //메인에 서버작업 시켜보기
        binding.btn5.setOnClickListener {
            CoroutineScope(Dispatchers.Main).launch {
                //네트워크 이미지 불러오기 .. 에러 ! 메인 스레드는 네트워크 작업 불가능
                val url = URL("https://cdn.pixabay.com/photo/2023/05/24/05/06/dog-8014047_1280.jpg")
                val bm : Bitmap = BitmapFactory.decodeStream(url.openStream())

                binding.iv.setImageBitmap(bm)
            }

            Toast.makeText(this, "main", Toast.LENGTH_SHORT).show()
        }

        //Dispatchers.IO : 입출력
        binding.btn6.setOnClickListener {
            CoroutineScope(Dispatchers.IO).launch {
                //네트워크 이미지 불러오기 .. 에러 ! 메인 스레드는 네트워크 작업 불가능
                val url = URL("https://cdn.pixabay.com/photo/2023/05/24/05/06/dog-8014047_1280.jpg")
                val bm : Bitmap = BitmapFactory.decodeStream(url.openStream())

                //유아이를 건드려서 에러
                //binding.iv.setImageBitmap(bm)

                //유아이는 메인 스레드가 하도록 시켜야됨!!!!
                withContext(Dispatchers.Main) {
                    binding.iv.setImageBitmap(bm)
                }
            }

            Toast.makeText(this, "io", Toast.LENGTH_SHORT).show()
        }



        // 스레드 동기 맞추기
        //1. 동시에 작업하기
        binding.btn7.setOnClickListener {
            CoroutineScope(Dispatchers.Default).launch {

                //작업 1
                launch {
                    for (n1 in 1000..1010) {
                        Log.d("TAG","n1: $n1")
                        delay(500)
                    }
                }

                //작업 2
                launch {
                    for (n2 in 2000..2010) {
                        Log.d("TAG","n2: $n2")
                        delay(500)
                    }
                }

            }
        }

        // 2. 동기 맞추기
        binding.btn8.setOnClickListener {
            CoroutineScope(Dispatchers.Default).launch {

                //작업 1
                launch {
                    for (n1 in 1000..1010) {
                        Log.d("TAG","n1: $n1")
                        delay(500)
                    }
                }.join() //작업1이 끝날 떄 까지 다른 코루틴 실행 대기

                //작업 2
                launch {
                    for (n2 in 2000..2010) {
                        Log.d("TAG","n2: $n2")
                        delay(500)
                    }
                }

                // launch 말고 async도 있음
//                async {
//
//                }
            }
        }


        //suspend 함수
        binding.btn9.setOnClickListener {
            CoroutineScope(Dispatchers.Default).launch {
                someTask()
            }
        }


        //코루틴 제어
        var job : Job? = null //참조 변수 만들기 Job으로
        // 스타트
        binding.btn10.setOnClickListener {
            //스코프르 런치하면 그것을 참조 할 수 있음
            job = CoroutineScope(Dispatchers.Default).launch {
                for (n1 in 300..310) {
                    Log.d("TAG","n1: $n1")
                    delay(500)
                }
            }
        }

        //캔슬 - 작업 캔슬 시킬 때 사용
        binding.btn11.setOnClickListener {
            job?.cancel()
        }

    }

    // delay는 코루틴의 기능임 suspend 키워드를 붙이면 사용 가능해짐
    // 코루틴 스코프 범위 밖에서 코루틴의 기능을 사용할 때 함수를 suspend 함수로 만들면 해결할 수 있음
    suspend fun someTask() {
        for (n in 1000..1010) {
            Log.d("TAG", "someTask : $n")
            delay(500)
        }
    }

}

[3] lifecycleScope

  • 안드로이드적 코루틴 스코프!!!!
    -> 별도의 라이브러리 추가 필요

    lifecycle-runtime-ktx
    버전이 안맞아서 오류날 수 있음

    현재 2.5.1 버전까진 잘됨 , 2.6.1 버전 오류남...

  • 액티비티 또는 프래그먼트의 라이프사이클과 함께 반응하는 코루틴 존재

1. CoroutineScope와 LifecycleScope 차이


CoroutineScope은 화면 안보여도 계속 실행
LifecycleScope는 화면 꺼지면 멈춤

package com.bsj0420.ex101coroutineadroid

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

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

        //기본 코틀린 언어에서 지원하는 코루틴은 2종류 : GlobalScope, CoroutineScope
        //안드로이드는 액티비티 또는 프래그먼트의 라이프사이클이 존재함
        //이에 함께 반응하는 코루틴 존재
        // lifecycleScope / ViewModelScope -- 별도의 라이브러리 추가 필요

        //1. CoroutineScope
        findViewById<Button>(R.id.btn).setOnClickListener {
            clickBtn1()
        }

        //2. LifecycleScope
        findViewById<Button>(R.id.btn2).setOnClickListener {
            clickBtn2()
        }

    }

    private fun clickBtn1() {
        CoroutineScope(Dispatchers.Default).launch {
            for (n in 0 .. 20) {
                Log.d("TAG", "CoroutineScope : $n")
                delay(500)
            }
        }
    }

    //안드로이드의 라이프사이클에 같이 제어되는 코루틴 스코트 : LifecycleScope
    //알아서 디스패쳐 적절히 찾는다
    //onCreate 부터 onDestory 가지의 액티비티 라이프사이플 Owner(액티비티, 프래그먼트)사용
    private fun clickBtn2() {
        this.lifecycleScope.launch {
            for (n in 100 .. 120) {
                Log.d("TAG", "lifecycleScope : $n")
                delay(500)
            }
        }

    }

    override fun onBackPressed() {
        finish()
    }
}



3. LifecycleScope when resume

얠 쓰는 걸 가장 선호함!~!!!!!!!

  • onResume() 부터 onPause() 동안에만 코루틴 동작함 onPause()되면 자동으로 일시정지
    다시 onResume() 되면 자동으로 이어서 실행함!!!!

package com.bsj0420.ex101coroutineadroid

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

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

    private fun clickBtn3() {
        // onResume() 부터 onPause() 동안에만 코루틴 동작함
        // onPause()되면 자동으로 일시정지
        // 다시 onResume() 되면 자동으로 이어서 실행함!!!!
        lifecycleScope.launchWhenResumed {
            loopTask()
        }
    }

    suspend fun loopTask() {
        for (n in 0 .. 20) {
            Log.d("TAG", "lifecycleScope when resume : $n")
            delay(500)
        }
    }




    override fun onBackPressed() {
        finish()
    }
}
profile
보조기억장치

0개의 댓글