Exercise: CounterContext
Implement a CounterContext class that is a mutable counter and can always be asked for a new value by using the next function. It should be possible to create multiple instances of CounterContext, each of which should have its own counter.
Example usage:
fun main(): Unit = runBlocking(CounterContext()) {
println(coroutineContext[CounterContext]?.next()) // 0
println(coroutineContext[CounterContext]?.next()) // 1
launch {
println(coroutineContext[CounterContext]?.next())// 2
println(coroutineContext[CounterContext]?.next())// 3
}
launch(CounterContext()) {
println(coroutineContext[CounterContext]?.next())// 0
println(coroutineContext[CounterContext]?.next())// 1
}
}
The simplest way to create a custom context is by creating a class that extends the AbstractCoroutineContextElement class. This class implements the CoroutineContext.Element interface, and it is a good starting point for creating custom contexts. It requires the value that is used as a key for this context. It is recommended to use a companion object for this purpose. Here is an example of a simple context:
class MyContext : AbstractCoroutineContextElement(MyContext){
companion object : CoroutineContext.Key<MyContext>
}
This problem can either be solved in the below playground or you can clone
kotlin-exercises project and solve it locally. In the project, you can find code template for this exercise in coroutines/context/CounterContext.kt. You can find there example usage and unit tests.
Once you are done with the exercise, you can check your solution
here.
Playground
import kotlinx.coroutines.*
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.*
import kotlin.test.assertEquals
// TODO
fun main(): Unit = runBlocking(CounterContext()) {
println(coroutineContext[CounterContext]?.next()) // 0
println(coroutineContext[CounterContext]?.next()) // 1
launch {
println(coroutineContext[CounterContext]?.next())// 2
println(coroutineContext[CounterContext]?.next())// 3
}
launch(CounterContext()) {
println(coroutineContext[CounterContext]?.next())// 0
println(coroutineContext[CounterContext]?.next())// 1
}
}
class CounterContextTests {
@Test
fun `should return next numbers in the same coroutine`() = runBlocking<Unit>(CounterContext()) {
assertEquals(0, coroutineContext[CounterContext]?.next())
assertEquals(1, coroutineContext[CounterContext]?.next())
assertEquals(2, coroutineContext[CounterContext]?.next())
assertEquals(3, coroutineContext[CounterContext]?.next())
assertEquals(4, coroutineContext[CounterContext]?.next())
}
@Test
fun `should have independent counter for each instance`() = runBlocking<Unit> {
launch(CounterContext()) {
assertEquals(0, coroutineContext[CounterContext]?.next())
assertEquals(1, coroutineContext[CounterContext]?.next())
assertEquals(2, coroutineContext[CounterContext]?.next())
}
launch(CounterContext()) {
assertEquals(0, coroutineContext[CounterContext]?.next())
assertEquals(1, coroutineContext[CounterContext]?.next())
assertEquals(2, coroutineContext[CounterContext]?.next())
}
}
@Test
fun `should propagate to the child`() = runBlocking<Unit>(CounterContext()) {
assertEquals(0, coroutineContext[CounterContext]?.next())
launch {
assertEquals(1, coroutineContext[CounterContext]?.next())
launch {
assertEquals(2, coroutineContext[CounterContext]?.next())
}
launch(CounterContext()) {
assertEquals(0, coroutineContext[CounterContext]?.next())
assertEquals(1, coroutineContext[CounterContext]?.next())
launch {
assertEquals(2, coroutineContext[CounterContext]?.next())
}
}
}
}
}