web-dev-qa-db-ja.com

AndroidでKotlinコルーチンを正しい方法で

非同期を使用してアダプター内のリストを更新しようとしていますが、定型文が多すぎることがわかります。

Kotlinコルーチンを使用する正しい方法ですか?

これをさらに最適化できますか?

fun loadListOfMediaInAsync() = async(CommonPool) {
        try {
            //Long running task 
            adapter.listOfMediaItems.addAll(resources.getAllTracks())
            runOnUiThread {
                adapter.notifyDataSetChanged()
                progress.dismiss()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            runOnUiThread {progress.dismiss()}
        } catch (o: OutOfMemoryError) {
            o.printStackTrace()
            runOnUiThread {progress.dismiss()}
        }
    }
37
Sai Kiran

この質問に何日も苦労した後、Kotlinを使用したAndroidアクティビティの最も単純で明確な非同期待機パターンは次のとおりだと思います。

override fun onCreate(savedInstanceState: Bundle?) {
    //...
    loadDataAsync(); //"Fire-and-forget"
}

fun loadDataAsync() = async(UI) {
    try {
        //Turn on busy indicator.
        val job = async(CommonPool) {
           //We're on a background thread here.
           //Execute blocking calls, such as retrofit call.execute().body() + caching.
        }
        job.await();
        //We're back on the main thread here.
        //Update UI controls such as RecyclerView adapter data.
    } 
    catch (e: Exception) {
    }
    finally {
        //Turn off busy indicator.
    }
}

コルーチンのGradle依存関係は、kotlin-stdlib-jre7kotlinx-coroutines-Androidのみです。

注:job.await()は例外を再スローしますが、job.join()はそうしないため、await()の代わりにjoin()を使用します。 join()を使用する場合、ジョブの完了後にjob.isCompletedExceptionallyを確認する必要があります。

後付け呼び出しconcurrentを開始するには、次のようにします。

val jobA = async(CommonPool) { /* Blocking call A */ };
val jobB = async(CommonPool) { /* Blocking call B */ };
jobA.await();
jobB.await();

または:

val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
37
KTCO

コルーチンを起動する方法

kotlinx.coroutinesライブラリーでは、launchまたはasync関数を使用して新しいコルーチンを開始できます。

概念的には、asynclaunchと同じです。これは、他のすべてのコルーチンと同時に動作する軽量スレッドである別のコルーチンを開始します。

違いは、launchはJobを返し、結果の値を保持しないのに対し、asyncDeferredを返します-結果を提供する約束を表す軽量のノンブロッキングFuture後。遅延値で.await()を使用して最終結果を取得できますが、DeferredJobでもあるため、必要に応じてキャンセルできます。

コルーチンコンテキスト

Androidでは、通常2つのコンテキストを使用します。

  • Android main uiContextスレッド(親コルーチン用)に実行をディスパッチするUI
  • bgContextは、バックグラウンドスレッドで実行をディスパッチします(子コルーチンの場合)

//dispatches execution onto the Android main UI thread
private val uiContext: CoroutineContext = UI

//represents a common pool of shared threads as the coroutine dispatcher
private val bgContext: CoroutineContext = CommonPool

次の例では、CommonPoolbgContextを使用して、並行して実行するスレッドの数をRuntime.getRuntime.availableProcessors()-1の値に制限します。したがって、コルーチンタスクがスケジュールされているが、すべてのコアが占有されている場合、それはキューに入れられます。

newFixedThreadPoolContextまたは独自のキャッシュスレッドプールの実装の使用を検討することもできます。

launch + async(タスクの実行)

private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread

    val task = async(bgContext) { dataProvider.loadData("Task") }
    val result = task.await() // non ui thread, suspend until finished

    view.showData(result) // ui thread
}

launch + async + async(2つのタスクを順番に実行)

注:task1とtask2は順番に実行されます。

private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread

    // non ui thread, suspend until task is finished
    val result1 = async(bgContext) { dataProvider.loadData("Task 1") }.await()

    // non ui thread, suspend until task is finished
    val result2 = async(bgContext) { dataProvider.loadData("Task 2") }.await()

    val result = "$result1 $result2" // ui thread

    view.showData(result) // ui thread
}

launch + async + async(2つのタスクを並行して実行)

注:task1とtask2は並行して実行されます。

private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread

    val task1 = async(bgContext) { dataProvider.loadData("Task 1") }
    val task2 = async(bgContext) { dataProvider.loadData("Task 2") }

    val result = "${task1.await()} ${task2.await()}" // non ui thread, suspend until finished

    view.showData(result) // ui thread
}

コルーチンをキャンセルする方法

関数loadDataは、キャンセルできるJobオブジェクトを返します。親コルーチンがキャンセルされると、その子もすべて再帰的にキャンセルされます。

dataProvider.loadDataが進行中にstopPresenting関数が呼び出された場合、関数view.showDataは呼び出されません。

var job: Job? = null

fun startPresenting() {
    job = loadData()
}

fun stopPresenting() {
    job?.cancel()
}

private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread

    val task = async(bgContext) { dataProvider.loadData("Task") }
    val result = task.await() // non ui thread, suspend until finished

    view.showData(result) // ui thread
}

完全な答えは私の記事で利用可能です Android Coroutine Recipes

30
Dmytro Danylyk

UIの代わりにAndroidアプリケーションにCommonPoolコンテキストを使用すると、runOnUiThread { ... }を取り除くことができると思います。

UIコンテキストは、 kotlinx-coroutines-Android モジュールによって提供されます。

8
Steffen

別のオプションもあります。 Anko libraryを使用すると、次のようになります

doAsync { 

    // Call all operation  related to network or other ui blocking operations here.
    uiThread { 
        // perform all ui related operation here    
    }
}

このようにアプリグラドルにAnkoの依存関係を追加します。

compile "org.jetbrains.anko:anko:0.10.3"
5
Suraj Nair

Sdeffが言ったように、UIコンテキストを使用すると、そのコルーチン内のコードはデフォルトでUIスレッドで実行されます。また、別のスレッドで命令を実行する必要がある場合は、run(CommonPool) {}を使用できます

さらに、メソッドから何も返す必要がない場合は、launch(UI)の代わりに関数async(UI)を使用できます(前者はJobを返し、後者はDeferred<Unit>を返します)。

例は次のとおりです。

fun loadListOfMediaInAsync() = launch(UI) {
    try {
        withContext(CommonPool) { //The coroutine is suspended until run() ends
            adapter.listOfMediaItems.addAll(resources.getAllTracks()) 
        }
        adapter.notifyDataSetChanged()
    } catch(e: Exception) {
        e.printStackTrace()
    } catch(o: OutOfMemoryError) {
        o.printStackTrace()
    } finally {
        progress.dismiss()
    }
}

さらにヘルプが必要な場合は、 kotlinx.coroutinesのメインガイド と、さらに coroutinesのガイド+ UI を読むことをお勧めします。

3
David Olmos

上記の答えはすべて正しいですが、kotlinx.coroutinesからUIの正しいインポートを見つけるのに苦労していました。UIからのAnkoと競合していました。その

import kotlinx.coroutines.experimental.Android.UI
1
Max

コトリンコルーチンを使用する正しい方法は次のとおりです。コルーチンスコープは、すべての子コルーチンが実行を完了するまで、現在のコルーチンを単に中断します。この例は、child coroutineparent coroutine内でどのように機能するかを明示的に示しています。

説明付きの例:

fun main() = blockingMethod {                    // coroutine scope         

    launch { 
        delay(2000L)                             // suspends the current coroutine for 2 seconds
        println("Tasks from some blockingMethod")
    }

    coroutineScope {                             // creates a new coroutine scope 

        launch {
            delay(3000L)                         // suspends this coroutine for 3 seconds
            println("Task from nested launch")
        }

        delay(1000L)
        println("Task from coroutine scope")     // this line will be printed before nested launch
    } 

    println("Coroutine scope is over")           // but this line isn't printed until nested launch completes
}

お役に立てれば。

0
ARGeo

バックグラウンドスレッドから何かを返すには、asyncを使用します。

launch(UI) {
   val result = async(CommonPool) {
      //do long running operation   
   }.await()
   //do stuff on UI thread
   view.setText(result)
}

バックグラウンドスレッドが何も返さない場合

launch(UI) {
   launch(CommonPool) {
      //do long running operation   
   }.await()
   //do stuff on UI thread
}
0
Rocky