Main (main thread). In it, we will extract the image from the IO stream and send this image back to Main for processing. launch(Dispatchers.Main) { val image = withContext(Dispatchers.IO) { getImage() } // IO imageView.setImageBitmap(image) // } getImage is executed in a dedicated pool of IO threads, the main thread is free and can take on any other task! The withContext function suspends the current coruntine while its action is running ( getImage() ). As soon as getImage() returns and the looper from the main thread becomes available, the coroutine will resume work in the main thread and call imageView.setImageBitmap(image) . val job = launch(Dispatchers.Main) { val deferred1 = async(Dispatchers.Default) { getFirstValue() } val deferred2 = async(Dispatchers.IO) { getSecondValue() } useValues(deferred1.await(), deferred2.await()) } job.join() // , async is similar to launch , but returns deferred (Kotlin entity equivalent to Future ), so its result can be obtained using await() . When called without parameters, it works in the default context for the current scope.launch function returns a Job , which can be used to wait until the operation completes — this is done using the join() function. It works like in any other language, with the proviso that it simply suspends coruntine, and does not block the flow .Main looks in java, that is, runOnUiThread: public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); // } else { action.run(); // } } Main context for Android is a Handler based dispatcher. So this is really a very suitable implementation: launch(Dispatchers.Main) { ... } vs launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { ... } // kotlinx 0.26: launch(Dispatchers.Main.immediate) { ... } launch(Dispatchers.Main) sends Runnable to Handler , so its code is not executed immediately.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) will immediately execute its lambda expression in the current thread.Dispatchers.Main ensures that when the corutin resumes work, it will be sent to the main stream ; In addition, Handler is used here as the native Android implementation for sending app events to the loop. val Main: HandlerDispatcher = HandlerContext(mainHandler, "Main") val job = Job() val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> whatever(throwable) } launch(Disaptchers.Default+exceptionHandler+job) { ... } job.cancel() will cancel all coroutines whose parent is job . A exceptionHandler will receive all the exceptions thrown in these quiches.coroutineScope interface simplifies error handling:async example, if the value could not be retrieved, another task continued to work - we have a damaged state, and something needs to be done about it.coroutineScope , the useValues function will be called only if the retrieval of both values ​​was successful. Also, if deferred2 fails, deferred1 will be canceled. coroutineScope { val deferred1 = async(Dispatchers.Default) { getFirstValue() } val deferred2 = async(Dispatchers.IO) { getSecondValue() } useValues(deferred1.await(), deferred2.await()) } CoroutineContext context and use it.CoroutineScope interface: open class ScopedViewModel : ViewModel(), CoroutineScope { protected val job = Job() override val coroutineContext = Dispatchers.Main+job override fun onCleared() { super.onCleared() job.cancel() } } CoroutineScope :launch or async default dispatcher now becomes the dispatcher of the current scope. launch { val foo = withContext(Dispatchers.IO) { … } // - CoroutineContext … } launch(Dispatchers.Default) { // - … } GlobalScope.launch(Dispatchers.Main) { // - . … } Main Manager: object AppScope : CoroutineScope by GlobalScope { override val coroutineContext = Dispatchers.Main.immediate } Dispatchers.Default (and Main ...) - Dispatchers.IO is designed for thisDispatchers.Default based on ForkJoinPool , which appeared in Android 5+Channelconceptually very similar toBlockingQueue. The key difference is that it does not block the put operation, it provides a suspendingsend(or nonblockingoffer), and instead of blocking a take operation it provides a suspendingreceive.
Actor .Actor , again, is very similar to Handler : we define the context of the korutina (that is, the stream in which we are going to perform the actions) and work with it in a sequential order.actor will redirect any command to the cortina channel. It guarantees the execution of a command and restricts operations in its context . This approach perfectly helps to get rid of synchronize calls and keep all streams free! protected val updateActor by lazy { actor<Update>(capacity = Channel.UNLIMITED) { for (update in channel) when (update) { Refresh -> updateList() is Filter -> filter.filter(update.query) is MediaUpdate -> updateItems(update.mediaList as List<T>) is MediaAddition -> addMedia(update.media as T) is MediaListAddition -> addMedia(update.mediaList as List<T>) is MediaRemoval -> removeMedia(update.media as T) } } } // fun filter(query: String?) = updateActor.offer(Filter(query)) // suspend fun filter(query: String?) = updateActor.send(Filter(query)) sealed class Update object Refresh : Update() class Filter(val query: String?) : Update() class MediaAddition(val media: Media) : Update() job.cancel() when destroying activity. class MyActivity : AppCompatActivity(), CoroutineScope { protected val job = SupervisorJob() // Job override val coroutineContext = Dispatchers.Main.immediate+job override fun onDestroy() { super.onDestroy() job.cancel() // } } SupervisorJob class is similar to the normal Job with the only exception that cancellation only extends in the downstream direction.Activity , when one of them refuses.CoroutineContext from any View in CoroutineScope . val View.coroutineContext: CoroutineContext? get() = (context as? CoroutineScope)?.coroutineContext setOnClick function creates a combined actor to control its onClick actions. In the case of multiple clicks, intermediate actions will be ignored, thus eliminating ANR errors (the application is not responding), and these actions will be performed within the scope of the Activity . Therefore, if you destroy the activity, all this will be canceled. fun View.setOnClick(action: suspend () -> Unit) { // val scope = (context as? CoroutineScope)?: AppScope val eventActor = scope.actor<Unit>(capacity = Channel.CONFLATED) { for (event in channel) action() } // setOnClickListener { eventActor.offer(Unit) } } Channel to be Conflated , so that it ignores part of the events if there are too many of them. You can replace it with Channel.UNLIMITED if you prefer to queue events without losing any of them, but you still want to protect the application from ANR errors. val LifecycleOwner.untilDestroy: Job get() { val job = Job() lifecycle.addObserver(object: LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy() { job.cancel() } }) return job } // GlobalScope.launch(Dispatchers.Main, parent = untilDestroy) { /* ! */ } Channel .requestBrowsing(url, listener) initiates a parsing of the folder located at url .listener receives onMediaAdded(media: Media) for any media file found in this folder.listener.onBrowseEnd() is called upon completion of the folder parsingrefresh function in the content provider for the VLC browser: private val refreshList = mutableListOf<Media>() fun refresh() = requestBrowsing(url, refreshListener) private val refreshListener = object : EventListener{ override fun onMediaAdded(media: Media) { refreshList.add(media)) } override fun onBrowseEnd() { val list = refreshList.toMutableList() refreshList.clear() launch { dataset.value = list parseSubDirectories() } } } refresh . Now browser callbacks will only send media to this channel, and then close it.refresh function has become clearer. It creates a channel, calls the VLC browser, then builds a list of media files and processes it.select or consumeEach you can use for to wait for the media, and this cycle will break as soon as the browserChannel channel closes. private lateinit var browserChannel : Channel<Media> override fun onMediaAdded(media: Media) { browserChannel.offer(media) } override fun onBrowseEnd() { browserChannel.close() } suspend fun refresh() { browserChannel = Channel(Channel.UNLIMITED) val refreshList = mutableListOf<Media>() requestBrowsing(url) // for (media in browserChannel) refreshList.add(media) // dataset.value = refreshList parseSubDirectories() } retrofitSuspendCall function wraps the request for a Retrofit Call to make it a suspend function.suspendCoroutine we call the Call.enqueue method and suspend the coruntine. The callback provided in this way will apply to continuation.resume(response) to resume the quortenine response from the server as soon as it is received.retrofitSuspendCall , in order to use them to return query results. suspend inline fun <reified T> retrofitSuspendCall(request: () -> Call <T> ) : Response <T> = suspendCoroutine { continuation -> request.invoke().enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { continuation.resume(response) } override fun onFailure(call: Call<T>, t: Throwable) { continuation.resumeWithException(t) } }) } suspend fun browse(path: String?) = retrofitSuspendCall { ApiClient.browse(path) } // ( Main) livedata.value = Repo.browse(path) Channel can be used in many other ways; Look at BroadcastChannel for more powerful implementations that may be useful to you.Channel or, for example, via Actor .Source: https://habr.com/ru/post/457224/
All Articles