Mark
12/21/2022, 6:25 AMwithTimeoutOrNull
to execute blocking code with a timeout. I’m doing this using a combination of runBlocking
and async
but I’m not clear about why using certain scopes results in the timeout not working. Here is code to demonstrate what I’m talking about: https://pl.kotl.in/af9mTAW9zbezrukov
12/21/2022, 6:43 AMasync(Dispatchers.Default)
for work1bezrukov
12/21/2022, 6:43 AMgildor
12/21/2022, 6:45 AMMark
12/21/2022, 6:47 AMrunInterruptible
which looks good. In reality, the withTimeout is being executed from a suspending function. So do I need to be careful with dispatchers regarding this?gildor
12/21/2022, 6:50 AMgildor
12/21/2022, 6:50 AMMark
12/21/2022, 6:52 AMblock
is run on a different thread to the timeout code?gildor
12/21/2022, 6:52 AMgildor
12/21/2022, 6:53 AMMark
12/21/2022, 6:54 AMgildor
12/21/2022, 6:55 AMgildor
12/21/2022, 6:56 AMMark
12/21/2022, 6:57 AMblock
?gildor
12/21/2022, 7:11 AMgildor
12/21/2022, 7:12 AMthere is a chance the timeout code is run on the same thread as theone thread dispatches one coroutine the same time
Mark
12/21/2022, 7:31 AM<http://Dispatchers.IO|Dispatchers.IO>
to be used for both timeout code and block, not working rather because of the fact the blocking code is not cooperative and so withTimeoutOrNull()
cannot finish, rather than because of some dispatcher choice issue: https://pl.kotl.in/jF9hnYSjqgildor
12/21/2022, 8:36 AMblock
is finished, it’s not an issue of dispatchersgildor
12/21/2022, 8:37 AMMark
12/21/2022, 8:47 AMwithTimeoutOrNull
. I thought by using Dispatchers.IO this would happen automatically, but if I printout Thread.currentThread().name
I see the same thread is being used, so it cannot work this way. So my question is why is the dispatcher using the same thread, and how to stop it from doing so?Mark
12/21/2022, 8:48 AMGlobalScope.async
to run the block
but I’m just trying to understand why.gildor
12/21/2022, 8:56 AMgildor
12/21/2022, 8:56 AMbut in my case I don’t want to cancel the blockIn this case you need to run it on separate scope
Mark
12/21/2022, 8:59 AMgildor
12/21/2022, 9:14 AMNick Allen
12/21/2022, 5:42 PMrunBlocking
creates a CoroutineScope
with a CoroutineDispatcher
that uses the current thread. It's essentially a thread pool of limit 1. While that thread pool is running Thread.sleep
it can't resume any coroutines.
GlobalScope
uses Dispatchers.Default
which is essentially a thread pool with a limit equal to your CPU count. If Dispatchers.Default
is running Thread.sleep
then the runBlocking
thread pool is not blocked, it can resume coroutines so withTimeoutOrNull
can immediately cancel its children.
GlobalScope
also has no Job
. It creates coroutines that are "roots" and are not connected to any other existing coroutine. You need to cancel these individually as they aren't part of an existing scope with a Job
. This makes it easy to start coroutines that "leak" meaning you don't need them to run any more but they are still going because they weren't explicitly cancelled.
All scoping suspend functions like withTimeoutOrNull
or withContext
or coroutineScope
(little c) wait for child coroutines to finish before they resume. Even if withTimeoutOrNull
is able to immediately cancel it's children, coroutine cancellation is asynchronous and cooperative. If that child take awhile to finish, withTimeoutOrNull
will suspend until then.
So in one of your examples, the one thread is blocked sleeping so withTimeoutOrNull
can't even try to cancel
its child coroutines (thread is blocked sleeping).
In another example, withTimeoutOrNull
succeeds in asking its children to cancel but the child doesn't cooperate so withTimeoutOrNull
suspends until its child is finished.
In the examples where `withTimeoutOrNull resumes before the Thread.sleep
finishes, you are both sleeping on a different thread pool AND you are sleeping in a coroutine that is not a child of withTimeoutOrNull
.