How to pause a coroutine

Vasiliy Nikitin
MobilePeople
Published in
3 min readSep 22, 2022

In the Kotlin world, coroutines are the recommended way to start background tasks. In some cases, we may encounter the need to pause a long-running task. For example, a user may pause the download of a large file to temporarily reduce network load.

https://unsplash.com/photos/1k3vsv7iIIc

Look back: pausing background tasks in the Java world

The easiest (and the less efficient) way to run a background task in Java is to use threads. Any thread may be suspended and resumed using suspend() and resume() methods. But this way is deprecated and unsafe, so it is not recommended.

The better way is to use an additional variable with a pause flag. This method is more flexible and applicable to almost all concurrency approaches (Thread, Executors, AsyncTask, etc.).

Pausing a Java thread

The main disadvantage of this approach is that execution can only be paused at the point where you manually check the pause flag.

Yet another way is to use wait() and notify() but it is not available in Kotlin.

What about coroutines?

In the coroutines world, we can use the second approach with the pause flag to pause and resume a coroutine. You can manually check this flag and suspend the coroutine while this flag is set. But the main disadvantage of this method remains: the coroutine can be paused only at those points where you manually check the flag. Let’s try to find another way.

As you know, any coroutine can only be canceled at a suspension point. The suspension point is a place you call the suspend function.

More precisely, not a call to the suspend function itself, but the moment when the suspend function returns control to the coroutine state machine. If suspend function is support cancellation, it will return as soon as coroutine will be cancelled, otherwise after it has completed. For example, the suspendCoroutine function does not support cancellation, while suspendCancellableCoroutine does.

The same is about pausing. You can only pause a coroutine at its suspension point. The block of code between suspension points is executed atomically by the CoroutineDispatcher. CoroutineDispatcher is a required element of a CoroutineContext. So, if you want to pause the coroutine in any suspension point without additional checks, you should implement a special CoroutineDispatcher that will enqueue execution until the coroutine becomes resumed or delegate it to a real dispatcher.

Implementation

To implement this approach, you need to write at least two classes:

  1. Queue with tasks that are postponed while the coroutine is paused.
  2. CoroutineDispatcher, which enqueues tasks if the coroutine is suspended or delegates its execution to the real dispatcher otherwise.

The queue must be a CoroutineContext element because we need to get a single control point for pausing a coroutine with all nested coroutines and even when the dispatcher changes.

Summary

As a result, we have a flexible mechanism for pausing any coroutine at any suspending point. The only thing you have to remember is that any change to the dispatcher must be wrapped by a PausingDispatcher, because a CoroutineContext can contain one and only one dispatcher, so any new dispatcher replaces the old one.

You can find the ready-to-use library that provides the more complex implementation of this approach on GitHub.

--

--