Thomas
06/07/2019, 12:01 PMcollect
and cancel
functions are both called on the main thread. I tried this with both version 1.2.1 and 1.3.0-M1
var cancelled = false
val job = GlobalScope.launch(Dispatchers.Main) {
val flow = flow {
while (true) {
emit(Unit)
}
}
flow.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
.collect {
if (cancelled) { // main thread
throw IllegalStateException()
}
}
}
GlobalScope.launch(Dispatchers.Main) {
delay(1000)
job.cancel() // main thread
cancelled = true
}
Thomas
06/07/2019, 12:02 PMAllan Wang
06/07/2019, 12:12 PMThomas
06/07/2019, 12:19 PMAllan Wang
06/07/2019, 12:21 PMThomas
06/07/2019, 12:21 PMsingleThread
in your test?
val singleThread = newSingleThreadContext("single thread")
Allan Wang
06/07/2019, 12:24 PMval singleThread = newSingleThreadContext("single thread")
var cancelled = false
val job = GlobalScope.launch(singleThread) {
val flow = flow {
while (true) {
emit(Unit)
}
}
flow.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
.collect { if (!isActive) throw java.lang.IllegalStateException() }
}
GlobalScope.launch(singleThread) {
delay(1000)
job.cancel() // main thread
cancelled = true
}
?
Using singleThread
in unit tests still doesn’t crash for meAllan Wang
06/07/2019, 12:24 PM!isActive
)Thomas
06/07/2019, 12:26 PMThomas
06/07/2019, 12:26 PMIt could be that your collect action executes before the job is cancelled but after you’ve toggled your booleanThen how should I cancel it in the onDestroy method and be 100% sure it does not execute anymore? I am getting null pointer exceptions in my project because the view is set to null when the job is cancelled. If you replace the` boolean` with a
View
object, how should I solve this then?Allan Wang
06/07/2019, 12:26 PMThomas
06/07/2019, 12:29 PMAllan Wang
06/07/2019, 12:33 PMisActive
, and avoid the check in your collector?Allan Wang
06/07/2019, 12:33 PMThomas
06/07/2019, 12:37 PMval job = GlobalScope.launch(Dispatchers.Main) {
val flow = flow {
while (isActive) {
emit(Unit)
}
}
flow.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
.collect {
if (!isActive) {
throw IllegalStateException("This exception is thrown from the sample code.")
}
}
}
GlobalScope.launch(Dispatchers.Main) {
delay(1000)
job.cancel() // main thread
}
because this code still crashes when running on my device.Allan Wang
06/07/2019, 12:42 PMif (!isActive)
is needed. I guess there could still be a case where you emit before cancellation and collect afterwards. I wonder if it’s best to just ignore values at that point (instead of throwing an exception)Allan Wang
06/07/2019, 12:46 PMflowWith
, which affects context downstream. Wouldn’t passing the main scope context (containing a job) allow it to cancel once you cancel the job?Thomas
06/07/2019, 12:54 PMI wonder if it’s best to just ignore values at that point@Allan Wang That could be a way to work around this, but I still don’t get why this is even possible. This is different behaviour than RxJava disposables. I would think that cancelling a job in a single thread (main) should make sure that items cannot be collected anymore on that same thread.
Allan Wang
06/07/2019, 1:16 PM@Test
fun a() {
val singleThread = newSingleThreadContext("single thread")
val job = GlobalScope.launch(singleThread) {
val flow = flow {
while (true) {
println("Emit ${Thread.currentThread().name}")
emit(Unit)
}
}
flow.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
.collect {
println(Thread.currentThread().name)
if (!isActive) throw java.lang.IllegalStateException()
}
}
runBlocking {
GlobalScope.launch(singleThread) {
delay(100)
job.cancel() // main thread
}.join()
}
}
Your comment makes sense to me. Cancelling from the single thread should stop subsequent runnables on the same threadThomas
06/07/2019, 1:26 PMThomas
06/07/2019, 1:28 PM@Test
fun a() {
// repeat code two times
repeat(2) {
val singleThread = newSingleThreadContext("single thread: $it")
val job = GlobalScope.launch(singleThread) {
val flow = flow {
while (true) {
println("Emit ${Thread.currentThread().name}")
emit(Unit)
}
}
flow.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
.collect {
println(Thread.currentThread().name)
if (!isActive) throw java.lang.IllegalStateException()
}
}
runBlocking {
GlobalScope.launch(singleThread) {
delay(100)
job.cancel() // main thread
}.join()
}
}
}
It does not crash. Now comment out the two println
calls and it does crash. Do you know why this happens?Allan Wang
06/07/2019, 1:36 PMprintln
. For me it crashed 2/10 times, one with println
and one without. I guess safest is to just check for an active coroutine and ignore otherwiseThomas
06/07/2019, 2:36 PMAllan Wang
06/07/2019, 4:11 PMThomas
06/10/2019, 8:23 AM