Pulau KotlinTeks sebelumnya dalam seri ini: tentang AsyncTask , tentang Loader , tentang Pelaksana dan EventBus , tentang RxJava .Jadi jam ini telah tiba. Ini adalah artikel di mana seluruh seri ditulis: penjelasan tentang bagaimana pendekatan baru bekerja "di bawah tenda". Jika Anda belum tahu cara menggunakannya, berikut adalah beberapa tautan berguna untuk memulai:
Dan setelah menguasai coroutine, Anda mungkin bertanya-tanya apa yang memungkinkan Kotlin untuk memberikan kesempatan ini dan bagaimana cara kerjanya. Harap dicatat bahwa di sini kami hanya akan fokus pada tahap kompilasi: Anda dapat menulis artikel terpisah tentang eksekusi.
Hal pertama yang perlu kita pahami adalah bahwa dalam corpus, sebenarnya tidak ada coroutine. Kompiler mengubah fungsi dengan pengubah sementara menjadi fungsi dengan parameter
Lanjutan . Antarmuka ini memiliki dua metode:
abstract fun resume(value: T) abstract fun resumeWithException(exception: Throwable)
Tipe T adalah tipe kembali dari fungsi penangguhan asli Anda. Dan inilah yang sebenarnya terjadi: fungsi ini dieksekusi di utas tertentu (kesabaran, kami juga mendapatkan ini), dan hasilnya diteruskan ke fungsi resume dari kelanjutan itu, dalam konteks di mana fungsi penangguhan dipanggil. Jika fungsi tidak menerima hasil dan melempar pengecualian, maka resumeWithException dilemparkan, melempar kesalahan ke kode panggilan.
Ok, tapi dari mana datangnya kelanjutan? Tentu saja, dari pembangun Corutin! Mari kita lihat kode yang membuat coroutine apa pun, misalnya, luncurkan:
public actual fun launch( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context, parent) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }
Di sini, builder membuat coroutine - sebuah instance dari kelas AbstractCoroutine, yang, pada gilirannya, mengimplementasikan antarmuka Continuation. Metode mulai milik antarmuka Pekerjaan. Tetapi menemukan definisi metode awal sangat sulit. Tapi kita bisa datang ke sini dari sisi lain. Pembaca yang penuh perhatian telah memperhatikan bahwa argumen pertama ke fungsi peluncuran adalah CoroutineContext, dan diset ke DefaultDispatcher secara default. Dispatcher adalah kelas yang mengontrol pelaksanaan coroutine, sehingga mereka sangat penting untuk memahami apa yang terjadi. Mari kita lihat deklarasi DefaultDispatcher:
public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool
Jadi, pada kenyataannya, ini adalah CommonPool, meskipun dok java memberi tahu kami bahwa ini dapat berubah. Apa itu CommonPool?
Ini adalah manajer coroutine yang menggunakan
ForkJoinPool sebagai implementasi dari ExecutorService. Ya, itu adalah: pada akhirnya, semua coroutine lambda Anda hanya Runnable, terperangkap dalam Pelaksana dengan sekelompok transformasi rumit. Tetapi iblis, seperti biasa, ada dalam perinciannya.
Fork? Atau bergabung?Menilai dari hasil survei di twitter saya, di sini saya perlu menjelaskan secara singkat apa itu FJP :)
Pertama-tama, ForkJoinPool adalah pelaksana modern yang dibuat untuk digunakan dengan aliran paralel Java 8. Tugas awalnya adalah paralelisme yang efisien ketika bekerja dengan Stream API, yang pada dasarnya berarti memecah aliran untuk memproses bagian dari data dan kemudian menggabungkannya ketika semua data telah diproses. Untuk mempermudah, bayangkan Anda memiliki kode berikut:
IntStream .range(1, 1_000_000) .parallel() .sum()
Jumlah aliran seperti itu tidak akan dihitung dalam satu aliran, sebagai gantinya, ForkJoinPool akan membagi rentang menjadi beberapa bagian (pertama menjadi dua bagian dari 500.000, kemudian masing-masing menjadi 250.000, dan seterusnya), menghitung jumlah masing-masing bagian, dan menggabungkan hasilnya menjadi satu. jumlah. Berikut ini visualisasi dari proses tersebut:
Utas dipisah untuk tugas yang berbeda dan digabung lagi setelah selesaiEfektivitas FJP didasarkan pada algoritma "pencurian pekerjaan": ketika utas tertentu kehabisan tugas, ia pergi ke antrian utas utas lainnya dan mencuri tugas mereka. Untuk pemahaman yang lebih baik, Anda dapat melihat
laporan Alexei Shipilev atau menonton
presentasi .
Ya, kami menyadari apa yang dilakukan coroutine kami! Tetapi bagaimana mereka berakhir di sana?
Ini terjadi di dalam metode pengiriman CommonPool #:
_pool.execute(timeSource.trackTask(block))
Metode pengiriman dipanggil dari metode resume (Nilai: T) di DispatchedContinuation. Kedengarannya asing! Kami ingat bahwa Lanjutan adalah antarmuka yang diimplementasikan dalam AbstractCoroutine. Tapi bagaimana hubungannya?
Triknya ada di dalam kelas CoroutineDispatcher. Ini mengimplementasikan antarmuka ContinuationInterceptor sebagai berikut:
public actual override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation)
Lihat? Anda memberikan blok sederhana untuk pembangun corutin. Anda tidak perlu mengimplementasikan antarmuka apa pun yang tidak ingin Anda ketahui. Perpustakaan coroutine menangani semua ini. Dia adalah
memotong eksekusi, menggantikan kelanjutan dengan DispatchedContinuation, dan mengirimkannya ke pelaksana, yang menjamin eksekusi kode Anda yang paling efisien.
Sekarang satu-satunya hal yang perlu kita tangani adalah bagaimana pengiriman dipanggil dari metode awal. Mari kita mengisi celah ini. Metode resume dipanggil dari startCoroutine dalam fungsi ekstensi blok:
public fun <R, T> (suspend R.() -> T).startCoroutine( receiver: R, completion: Continuation<T> ) { createCoroutineUnchecked(receiver, completion).resume(Unit) }
Dan startCoroutine, pada gilirannya, dipanggil oleh operator "()" dalam enumerasi CoroutineStart. Builder Anda menerimanya sebagai parameter kedua, dan defaultnya adalah CoroutineStart.DEFAULT. Itu saja!
Itulah alasan mengapa saya mengagumi pendekatan corutin: ini bukan hanya sintaksis yang spektakuler, tetapi juga implementasi yang brilian.
Dan bagi mereka yang telah membaca sampai akhir, mereka mendapatkan eksklusif: video laporan saya "Seorang pemain biola tidak diperlukan: kami menolak RxJava yang mendukung coroutine di Kotlin" dari konferensi Mobius . Selamat menikmati :)