专栏名称: NeXT
Android
目录
相关文章推荐
INTERNATIONAL IDEAL 筑梦求职  ·  不要钱的Audit证书,放Linkedin上 ... ·  昨天  
51好读  ›  专栏  ›  NeXT

「英」Kotlin 协程最佳实践示例

NeXT  · 掘金  ·  · 2018-11-21 02:14

正文

There are so many articles and talks out there which focused on what Kotlin Coroutines are and why, conceptually, they are useful. So, in this article, we’re not gonna see what Coroutines are instead of seeing some practical examples of how to use them…?

Theory without practice is empty; practice without theory is blind.

1. Changing threads in Coroutines

In UI based application like Android, Java Swing etc we need to update the UI only in the main thread and execute the network request in IO thread. Let’s see a simple example first:

  1. launch {
  2. try{
  3. val user = repo.fetchUserFromNetwork(userId).await()
  4. // handle user result
  5. } catch(e : Exception) {
  6. // TODO
  7. }
  8. }
Recommended for you

When launch is used without the parameter, it runs in the main thread. In this case, when the above code is executed it executes in the main thread which is not good. So, luckily the coroutines give us the Dispatchers that determine what thread or threads, the corresponding coroutine uses for its execution. In order to execute the above code in an IO thread, we need to add the dispatchers in launch coroutine builder.

  1. launch(Dispatchers.IO) {
  2. try {
  3. val user = repo.fetchUserFromNetwork(userId).await()
  4. // handle user result
  5. } catch(e : Exception) {
  6. // TODO
  7. }
  8. }

The IO dispatcher dispatches the network request in a background thread and it allocates additional threads on top of the ones allocated to the Default dispatcher.

Now, let’s say we want to update our UI inside the IO worker thread. With that in mind, the above example becomes:

  1. launch(Dispatchers.IO) {
  2. try {
  3. val user = repo.fetchUserFromNetwork(userId).await()
  4. withContext(Dispatchers.Main) {
  5. updateUi(user) // In main thread
  6. }
  7. // back to IO worker thread.
  8. } catch (e: Exception) {
  9. // TODO
  10. }
  11. }

The withContextimmediately shifts execution of the block into different thread inside the block, and back when it completes. Now in our case, you can see that we launched with IO dispatcher and everything in this scope is happening in that IO dispatcher except for the thing in the block where we’ve withContext. Only the code in the block withContext is executing in the main thread. Using withContext fulfills our needs, with a single function call and minimal object allocation, compared to creating a new coroutine with async or launch.

2. Coroutines inner Reference

Coroutines give you the ability while you’re in a coroutine builder to get information back from it. Let’s see an example:

  1. launch(Dispatcher.IO){
  2. val job = coroutineContext[Job]
  3. // use the job instance
  4. }

You see we’re getting the instance of a currently executing Job instance in the coroutine builder. You can get the Job instance in any of existing coroutine builder. Once you’ve Job instance you can do things like canceling the job, check its activity or check whether it has children .


3. Coroutines for Debugging

While developing your application you often need to know the name of the coroutine in which your code is executed. However, when coroutine is tied to the processing of the specific request or doing some background specific task, it is better to name it explicitly for debugging purposes.

  1. suspend fun getUserFromNetwork(userId : Int) : User
  2. suspend fun getUserAccountInfo(accountId : Int) : Account
  3. fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
  4. launch(Dispatchers.IO) {
  5. val user = async(CoroutineName("userFetchCoroutine")) {
  6. log("user fetch coroutine start")
  7. getUserFromNetwork(userId).await()
  8. }
  9. withContext(Dispatcher.Main) {
  10. log("")
  11. updateUI(user)
  12. }
  13. val account = async(CoroutineName("accountFetchCoroutine")) {
  14. log("account fetch coroutine start")
  15. getUserAccountInfo(user.accountId).await()
  16. }
  17. // handle user account info
  18. }
  19. Output Of above program
  20. [DefaultDispatcher-worker-1 @userFetchCoroutine#1] user fetch coroutine start
  21. [main]
  22. [DefaultDispatcher-worker-2 @accountFetchCoroutine#1] account fetch coroutine start

You see we’re passing the CoroutineName context element when initiating the async coroutine builder. In our above program output, you see it prints the CoroutineName, attached automatically with the worker id.

Note: The custom coroutine names only be displayed if the debugging mode is turned on. If you’re using IntelliJ go to Edit Configurations -> Configuration and paste the following virtual machine options in VM options: section and click apply.

  1. -Dkotlinx.coroutines.debug

4. Usage of suspending Function

Kotlin programming introduces a concept of suspending function via suspend modifier. We need to add the suspend modifier to a function makes it either asynchronous or non-blocking. Let’s take an example to find the Fibonacci Number with suspend function.

  1. fun main(args : Array<String>){
  2. val fibonacciNumber = getFibonacci(1000000)
  3. println(fibonacciNumber)
  4. }
  5. private suspend fun getFibonacci(range: Int): BigInteger {
  6. var first: BigInteger = BigInteger.ZERO
  7. var second: BigInteger = BigInteger.ONE
  8. for (i in 1 until range) {
  9. val sum = first + second
  10. first = second
  11. second = sum
  12. if (i == (range - 1))
  13. return first
  14. }
  15. return BigInteger.ZERO
  16. }

You see the above function takes 10-15 seconds to execute on my machine. Even though by adding the suspend keyword it still blocks the caller thread for quite a long time. Actually, if you write this function in IntelliJ IDEA , then you get “redundant ‘suspend’ modifier” warning, hinting that suspend modifier, by itself, does not magically turn to block functions into non-blocking ones.

So, in order to make this function into non-blocking, we need to implement this convention are provided by withContext coroutine builder. For example, the proper way to turn the getFibonacci into suspending one is:

  1. suspend fun main(args: Array<String>) = coroutineScope {
  2. val fibonacciNumber = getFibonacci(1000000)
  3. println(fibonacciNumber)
  4. }






请到「今天看啥」查看全文