Solution: CounterContext
Here is a simple solution:
class CounterContext :
AbstractCoroutineContextElement(CounterContext){
private var counter = 0
fun next(): Int = counter++
companion object : CoroutineContext.Key<CounterContext>
}
Here is a thread-safe solution:
class CounterContext :
AbstractCoroutineContextElement(CounterContext){
private val counter = AtomicInteger(0)
fun next(): Int = counter.getAndIncrement()
companion object : CoroutineContext.Key<CounterContext>
}
Example solution in playground
import kotlinx.coroutines.*
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.*
import kotlin.test.assertEquals
class CounterContext :
AbstractCoroutineContextElement(CounterContext){
private var counter = 0
fun next(): Int = counter++
companion object : CoroutineContext.Key<CounterContext>
}
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())
}
}
}
}
}