delay
doesn't actually do anything on its own, it just schedules the coroutine to be resumed at a later point in time. Continuations can, of course, be cancelled at any time.
runBlocking
, on the other hand, actually blocks a thread (which is why the compiler will complain about a blocking operation within a coroutine, and why you should never use runBlocking
outside of e.g. unit tests).
Note: Since main
can now be a suspending function, there's generally no need to use it whatsoever in your core application code.
This function should not be used from a coroutine
runBlocking
Coroutines, like threads, are not truly interruptible; they have to rely on cooperative cancellation (see also why stopping threads is bad). This means that cancellation doesn't actually do anything either; when you cancel a context, it simply notifies all of its child contexts to cancel themselves, but any code that is still running will continue to run until a cancellation check is reached.
It is important to realize that runBlocking
is not a suspend
function. It cannot be paused, resumed, or cancelled. The parent context is not passed to it by default (it receives an EmptyCoroutineContext
), so the coroutine used for the execution of runBlocking
won't react to anything that happens upstream.
When you write
while (i < 10) {
runBlocking { delay(500L) }
println("$isActive ${i++}")
}
there are no operations here that are cancellable. Therefore, the code never checks whether its context has been cancelled, so it will continue until it finishes.
delay
, however, is cancellable; as soon as its parent context is cancelled, it resumes immediately and throws an exception (i.e., it stops.)
Take a look at the generated code:
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
switch (this.label) {
case 0:
while (i.element < 10) {
BuildersKt.runBlocking$default( ... );
...
System.out.println(var3);
}
return Unit.INSTANCE;
default:
throw new IllegalStateException( ... );
}
}
Contrast this
while (i.element < 10) {
BuildersKt.runBlocking$default( ... );
...
System.out.println(var3);
}
with
do {
...
System.out.println(var3);
if (i.element >= 10) {
return Unit.INSTANCE;
}
...
} while (DelayKt.delay(500L, this) != var5);
Variable declarations and arguments omitted (...
) for brevity.
runBlocking
will terminate early if the current thread is interrupted, but again this is the exact same cooperative mechanism, except that it operates at the level of the thread rather than on a coroutine.