シンプルなAndroidアプリ


この単純なAndroidアプリはCoroutines(起動と非同期)を作成するなどの基本的なKotlin Coroutines使用を示します.
この記事はもともと1967年に出版されましたvtsen.hashnode.dev 2022年3月26日.
私はKotlin Coroutinesの基本的な使用を理解するために、この単純なAndroidアプリを作成しました.アプリケーションは、Coroutinesのジョブを作成し、それらを同時に実行する方法を示します.おそらく、使用ケースの100 %をカバーしないでしょう、おそらく少なくとも90 %?
アプリも簡単に使用してMVVM architecture なしでModel を参照).

アプリ概要



3つのボタンと2表示テキスト(左と右)このアプリではありません.

  • 両方の左のテキストと右のテキストUIを同時に

  • 左のテキストと右のテキストは- 1で無効な値として開始されます.
  • 起動または非同期ボタンがクリックされると、Coroutinesは0から左のテキストUIを更新するために作成されます→ 10から9と右のテキストUI→ 19
  • キャンセルボタンがクリックされると、両方のテキストが更新されず、値が- 1に設定されます.
  • キャンセルボタンがクリックされない場合、両方のテキストは最終値9および19まで更新され続ける
  • 次の2つの方法があります.
  • CoroutineScope.launch
  • CoroutineScope.async
  • コアスコープランチ


    コルーチンを作成するには、作成する必要がありますCoroutineScope ファースト.インViewModel , CorotineScope が既に生成されている(つまり、viewModelScope ). だからそれは非常に自分でそれを作成するのを使用することをお勧めします.つの利点は、すべてのCoroutineScope 子供は自動的にキャンセルされますViewModel が破壊される.
    // Create new coroutine - current job is the parent job
    currentJob = viewModelScope.launch {
    
        // Create a first new sub-coroutine - job1 is the child job
        val job1 = launch {
            ...
        }
    
        // Create a second new sub-coroutine - job2 is the child job
        val job2 = launch {
            ...
        }
    
        // job1 and job2 are coroutines that run concurrently
    
        // wait for both job1 and job2 to complete
        job1.join()
        job2.join()
        ... 
    }
    
    CoroutineScpoe.launch が非ブロッキング関数で、Job すぐに.並行性を達成するために、我々は呼び出すことができますCoroutineScpoe.launch 同じコルーチンスコープ内の複数回.job1 左のテキストUIとjob2 右のテキストUIを更新する責任があります.
    ジョブが完了するのを待ちたいなら、Job.join() サスペンド機能.ジョブが次の行に移動する前にジョブが完了するまで待機します.

    コアスコープ非同期


    私たちがそれが戻り値であるのを待ちたいCoroutineをつくるために、我々は使用しますCoroutineScope.async .
    同様CoroutineScpoe.launch , CoroutineScope.async は非ブロッキング機能です.返す代わりにJob , it returns Deferred<T> . の最後の行async block , 返り値T . 例えば、getData() リターンInt , したがって、T is Int 種類
    // Create new coroutine
    viewModelScope.launch {
        // Create a sub-coroutine with async
        val deferred = async {  
            ...
            getData()  
        }  
        // wait for async to return it's value
        data.value = deferred.await()
        ...
    }
    
    代わりにJob.join() , あなたの電話Deferred<T>.awailt() 待つCoroutineScope.async を返します.getData() .

    コアスコープwithcontext ()


    デフォルトでは、コルーチンはメイン/UIスレッドで実行されます.長い実行中のタスクを別のスレッドに移動してメイン/UIスレッドをブロックしないようにします.
    異なるスレッドに切り替えるには、CoroutineDispatcher . ここで一般的な定義済みCoroutineDispatcher を使うことができます.
  • Dispatchers.Main - メインスレッド
  • Dispatchers.Default - スレッドスレッド
  • Dispatchers.IO - ネットワークオペレーションスレッド
  • あなた自身の糸を使うために、あなたは新しい糸/newをつくることができますCoroutineDispatcher 使用newSingleThreadContext("MyOwnThread") . ほとんどの場合、定義済みのCoroutineDispatcher 十分です.
    コルーチンを作成するときlaunch or async , あなたはCoroutineDispatcher .
    viewModelScope.launch {
        // Create coroutine that runs on Dispatchers.Default thread 
        launch(Dispatchers.Default) {
            loadData()
        }
        // Create coroutine that runs on Dispatchers.Default thread 
        async(Dispatchers.Default) {
            loadData()
        }
    }
    
    しかし、より良い解決策はCoroutineScope.withContext() を指定するのではなく、CoroutineDispatcher Coroutineの作成中.サスペンド機能をmain/uiスレッドから安全に呼び出すことが推奨されます.
    private suspend fun loadData() {
        //Switches / moves the coroutine to different thread
        withContext(Dispatchers.Default) {
            ...
        }
    }
    

    Please note CoroutineScope.withContext() does NOT create a new coroutine. It moves the coroutines to a different thread.


    ジョブ.cancel ()


    コルーチンのジョブをキャンセルするにはJob.cancel() and Job.join() . ほとんどの時間、あなたは単に呼び出すことができますJob.cancelAndJoin() . 注意してくださいJob.cancelAndJoin() はサスペンド機能です.だから、コルーチンの内側にそれを呼び出す必要があります.
    fun onCancelButtonClick() {
        if (currentJob == null) return
        viewModelScope.launch() {
            currentJob!!.cancelAndJoin()
        }
    }
    

    currentJob is an existing coroutine job that was created before.


    コントリックス.コルーチン.yield ()


    注意する1つの重要なものは、Coroutineキャンセルは協力的です.コルーチンが非協力的なキャンセルであるなら、我々はそれをキャンセルすることができません.COOTINEは完了するまで実行されますJob.cancel() が呼び出されました.
    コルーチンを解除するには、次のようにします.
  • CoroutineScope.isActive
  • kotlinx.coroutines.yield()
  • CoroutineScope.isActive 必要CoroutineScope オブジェクトをコールし、コルーチンを終了するロジックを追加する必要があります.以来yield() 任意のsuspend関数で呼び出すことができます、私は個人的にそれを使用して好む.

    Please note the kotlinx.coroutines.delay() also make the coroutine cancellation cooperative.


    たとえば、以下のような長期的なタスクを持っている場合、CoroutineはJob.cancel() リクエスト.
    private suspend fun simulateLongRunningTask() {  
        repeat(1_000_000) {  
            Thread.sleep(100)  
        }  
    }
    
    それを受け入れるようにするJob.cancel() リクエストを追加する必要がありますyield() .
    private suspend fun simulateLongRunningTask() {  
        repeat(1_000_000) {  
            Thread.sleep(100)  
            yield()
        }  
    }
    

    コントリックス.コルーチン.JobCancellationException


    コルーチンがキャンセルされたときはkotlinx.coroutines.JobCancellationException 例外がスローされます.あなたは例外をキャッチし、いくつかのクリーンアップを実行することができます.
    currentJob = viewModelScope.launch {
        try {
            val job1 = launch {
                ...
            }
    
            val job2 = launch {
                ...
            }
    
            job1.join()
            job2.join()
    
        } catch (e: Exception) {
            // clean up here
            currentJob = null
        }   
    }
    

    コントリックス.コルーチン.コアテキスト


    coroutineをデバッグするには、最も簡単な方法です.kotlinx.coroutines.coroutineContext ログ記録に非常に便利です.これは、コルーチンとスレッド情報を提供します.

    Please note that it is a suspend property which can only be called from the suspend function.


    Utils.log() ユーティリティのサスペンド機能をラップする.Log.d() :
    object Utils {  
        suspend fun log(tag: String, msg: String) {  
            Log.d(tag, "$coroutineContext: $msg")  
        }  
    }
    
    //Usage
    Utils.log("ViewModel", "======= Created launch coroutine - onButtonClick() =======")
    
    LOGCAT出力の例:
    D/ViewModel: [StandaloneCoroutine{Active}@5261b32, Dispatchers.Main.immediate]: ======= Created launch coroutine - onButtonClick() =======
    

    いくつかの考え


    これまでのところ、私のすべてpersonal projects 上記のケースを使用するすべてのコルーチンを使用しないでください.私だけ使用CoroutineScope.launch and CoroutineScope.withContext() 私が欲しいものを成し遂げるには、どれが十分です.私は、私はしたい場合は、私は私がすることができますが、まだアプリケーションが完全に動作するCoroutineをキャンセルする必要はありません.
    [更新日: 2022年4月13日]joinAll() シーケンシャルにコードを実行するのではなく、いくつかのネットワークコールを並列化します.以下の例
    private suspend fun fetchArticlesFeed() : List<ArticleFeed> = coroutineScope {
        val results = mutableListOf<ArticleFeed>()
        val jobs = mutableListOf<Job>()
    
        for(url in urls) {
            val job = launch {
                val xmlString = webService.getXMlString(url)
                val articleFeeds = FeedParser().parse(xmlString)
                results.addAll(articleFeeds)
            }
    
            jobs.add(job)
        }
    
        jobs.joinAll()
    
        return@coroutineScope results
    }
    

    ソースコード


    githubリポジトリvinchamp77/Demo_CoroutinesBasics

    参考

  • kotlinx.coroutines.delay() vs Thread.sleep()
  • Kotlin Tips and Tricks