内容简介:We continue to highlight the upcoming changes in 1.4 release. In this blogpost, we want to describe a couple of important features related to coroutines:These changes are already available for you to try in the 1.4.0-RC release!
We continue to highlight the upcoming changes in 1.4 release. In this blogpost, we want to describe a couple of important features related to coroutines:
- New functionality to conveniently debug coroutines
- The ability to define deep recursive functions
These changes are already available for you to try in the 1.4.0-RC release!
Let’s dive into details.
Debugging coroutines
Coroutines are great for asynchronous programming (but not only for that), and many people already use them or are starting to use them. When you write code with coroutines, however, trying to debug them can be a real pain. Coroutines jump between threads. It can be difficult to understand what a specific coroutine is doing or to check its context. And in some cases, tracking steps over breakpoints simply doesn’t work. As a result, you have to rely on logging or mental effort to debug the code with coroutines. To address this issue, we’re introducing new functionality in the Kotlin plugin that aims to make debugging coroutines much more convenient.
The Debug Tool Window now contains a new Coroutines tab. It is visible by default, and you can switch it on and off:
In this tab, you can find information about both currently running and suspended coroutines. The coroutines are grouped by the dispatcher they are running on. If you started a coroutine with a custom name, you can find it by this name in the Tool Window. In the following example, you can see that the main coroutine is running (we’ve stopped on a breakpoint inside it), and the other four coroutines are suspended:
import kotlinx.coroutines.* fun main() = runBlocking { repeat(4) { launch(Dispatchers.Default + CoroutineName("Default-${'a' + it}")) { delay(100) val name = coroutineContext[CoroutineName.Key]?.name println("I'm '$name' coroutine") } } delay(50) // breakpoint println("I'm the main coroutine") }
With the new functionality, you can check the state of each coroutine and see the values of local and captured variables. This also works for suspended coroutines!
In this example, we check the values of the local variables of suspended coroutines:
import kotlinx.coroutines.* fun main() = runBlocking<Unit> { launch { val a = 3 delay(300) println(a) } launch { val b = 2 delay(200) println(b) } launch { val c = 1 delay(100) // breakpoint here: println(c) } }
Choose a suspended coroutine (click invokeSuspend
to see its state on that point) and the Variables
tab will show you the state of the local variables:
You can now see a full coroutine creation stack, as well as a call stack inside the coroutine:
Use the ‘Get Coroutines Dump’ option to get a full report containing the state of each coroutine and its stack:
At the moment, the coroutines dump is still rather simple, but we’re going to make it more readable and helpful in future versions.
Note that to make the debugger stop at a given breakpoint inside a coroutine, this breakpoint should have the “Suspend: All” option chosen for it:
To try this new functionality for debugging coroutines, you need to use the latest version of kotlinx.coroutines , 1.3.8-1.4.0-rc , and the latest version of the Kotlin plugin (e.g. 1.4.0-rc -release-IJ2020.1 -2 ).
The functionality is available only for Kotlin/JVM. If you encounter any problems (please don’t forget to share the details with us!), you can switch it off by opening Build, Execution, Deployment | Debugger | Data Views | Kotlin inPreferences and choosing Disable coroutines agent . For now, we’re releasing this functionality for debugging coroutines in the experimental state, and we’re looking forward to your feedback!
Defining deep recursive functions using coroutines
In Kotlin 1.4, you can define recursive functions and invoke them even when the call depth is greater than 100,000, using the standard library support based on coroutines!
Let’s first look at an ordinary recursive function, whose usage results in a StackOverflowError
when the recursion depth gets too high. After that, we’ll discuss how you can fix the problem and rewrite the function using the Kotlin standard library.
We’ll use a simple binary tree, where each Tree
node has a reference to its left
and right
children:
class Tree(val left: Tree?, val right: Tree?)
The depth of the tree is the length of the longest path from its root to its child nodes. It can be computed using the following recursive function:
fun depth(t: Tree?): Int = if (t == null) 0 else maxOf( depth(t.left), depth(t.right) ) + 1
The tree depth is the maximum of the depths of the left and right children increased by one. When the tree is empty it’s zero.
This function works fine when the recursion depth is small:
class Tree(val left: Tree?, val right: Tree?) fun depth(t: Tree?): Int = if (t == null) 0 else maxOf( depth(t.left), depth(t.right) ) + 1 fun main() { //sampleStart val tree = Tree(Tree(Tree(null, null), null), null) println(depth(tree)) // 3 //sampleEnd }
However, if you create a tree with a depth greater than 100,000, which in practice is not so uncommon, you’ll get StackOverflowError
as a result:
class Tree(val left: Tree?, val right: Tree?) fun depth(t: Tree?): Int = if (t == null) 0 else maxOf( depth(t.left), depth(t.right) ) + 1 //sampleStart fun main() { val n = 100_000 val deepTree = generateSequence(Tree(null, null)) { prev -> Tree(prev, null) }.take(n).last() println(depth(deepTree)) } /* Exception in thread "main" java.lang.StackOverflowError at FileKt.depth(File.kt:5) ... */ //sampleEnd
The problem is that the call stack gets too large. To solve this issue, you can use a VM option to increase the maximum stack size. However, while this might work for specific use cases, it’s not a practical solution for the general case.
Alternatively, you can rewrite the code and store results for intermediate calls by hand in the heap rather than on the stack. This solution works in most cases and is common in other languages. However, the resulting code becomes non-trivial and complicated, and the beauty and simplicity of the initial function are lost. You can find an example here .
Kotlin now provides a clean way to solve this problem based on the coroutines machinery.
The Kotlin library now includes the definition DeepRecursiveFunction
, which models recursive calls using the suspension mechanism:
class Tree(val left: Tree?, val right: Tree?) @OptIn(ExperimentalStdlibApi::class) val depthFunction = DeepRecursiveFunction<Tree?, Int> { t -> if (t == null) 0 else maxOf( callRecursive(t.left), callRecursive(t.right) ) + 1 } @OptIn(ExperimentalStdlibApi::class) fun depth(t: Tree) = depthFunction(t) fun main() { val n = 100_000 val deepTree = generateSequence(Tree(null, null)) { prev -> Tree(prev, null) }.take(n).last() println(depth(deepTree)) // 100000 }
You can compare the two versions, the initial one and the one using DeepRecursiveFunction
, to make sure that the logic remains the same. Your new function now becomes a variable of type DeepRecursiveFunction
, which you can call using the ‘invoke’ convention as depthFunction(t)
. The function body now becomes the body of the lambda argument of DeepRecursiveFunction
, and the recursive call is replaced with callRecursive
. These changes are straightforward and easy to make. Note that while the new depth
function uses coroutines under the hood, it is not itself a suspend
function.
Understanding how DeepRecursiveFunction
is implemented is interesting, but it is not necessary in order for you to use it and benefit from it. You can find the implementation details described in this blog post .
DeepRecursiveFunction
is a part of the Kotlin standard library, not part of the kotlinx.coroutines
library, since it’s not about asynchronous programming. At the moment this API is still experimental, so we’re looking forward to your feedback!
How to try it
As always, you can try Kotlin online at play.kotl.in .
In IntelliJ IDEA and Android Studio , you can update the Kotlin Plugin to version 1.4.0-RC. See how to do this .
If you want to work on existing projects that were created before installing the preview version, you need to configure your build for the preview version in Gradle or Maven . Note that unlike the previous preview versions, Kotlin 1.4.0-RC is also available directly from Maven Central. This means you won’t have to manually add the kotlin-eap
repository to your build files.
You can download the command-line compiler from the Github release page .
Share your feedback
We’ll be very thankful if you find and report bugs to ourissue tracker. We’ll try to fix all the important issues before the final release, which means you won’t need to wait until the next Kotlin release for your issues to be addressed.
You are also welcome to join the #eap channel in Kotlin Slack (get an invite here ). In this channel, you can ask questions, participate in discussions, and get notifications about new preview builds.
Let’s Kotlin!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。