非同期を使用してアダプター内のリストを更新しようとしていますが、定型文が多すぎることがわかります。
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()}
}
}
この質問に何日も苦労した後、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-jre7
、kotlinx-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(); };
コルーチンを起動する方法
kotlinx.coroutines
ライブラリーでは、launch
またはasync
関数を使用して新しいコルーチンを開始できます。
概念的には、async
はlaunch
と同じです。これは、他のすべてのコルーチンと同時に動作する軽量スレッドである別のコルーチンを開始します。
違いは、launchはJob
を返し、結果の値を保持しないのに対し、async
はDeferred
を返します-結果を提供する約束を表す軽量のノンブロッキングFuture後。遅延値で.await()
を使用して最終結果を取得できますが、Deferred
はJob
でもあるため、必要に応じてキャンセルできます。
コルーチンコンテキスト
Androidでは、通常2つのコンテキストを使用します。
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
次の例では、CommonPool
にbgContext
を使用して、並行して実行するスレッドの数を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
UI
の代わりにAndroidアプリケーションにCommonPool
コンテキストを使用すると、runOnUiThread { ... }
を取り除くことができると思います。
UI
コンテキストは、 kotlinx-coroutines-Android モジュールによって提供されます。
別のオプションもあります。 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"
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 を読むことをお勧めします。
上記の答えはすべて正しいですが、kotlinx.coroutines
からUI
の正しいインポートを見つけるのに苦労していました。UI
からのAnko
と競合していました。その
import kotlinx.coroutines.experimental.Android.UI
コトリンコルーチンを使用する正しい方法は次のとおりです。コルーチンスコープは、すべての子コルーチンが実行を完了するまで、現在のコルーチンを単に中断します。この例は、child coroutine
がparent 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
}
お役に立てれば。
バックグラウンドスレッドから何かを返すには、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
}