Previous article in this series:
This tutorial will explore methods for running tasks in specific sequential orders.
For example, say there you have a task where you want to download an archive with files, unpack it, and then process the files.
This can be done with three successive tasks:
First, let's make sure that tasks started in the usual way will be executed in parallel. We start three tasks at once:
val worker1: WorkRequest = OneTimeWorkRequestBuilder<Worker1>()
.build()
val worker2: WorkRequest = OneTimeWorkRequestBuilder<Worker2>()
.build()
val worker3: WorkRequest = OneTimeWorkRequestBuilder<Worker3>()
.build()
WorkManager
.getInstance(this)
.enqueue(listOf(worker1, worker2, worker3))
We look at the log:
2023-04-18 21:07:05 18984-19015 Worker2 D doWork: start
2023-04-18 21:07:05 18984-19016 Worker3 D doWork: start
2023-04-18 21:07:05 18984-19014 Worker1 D doWork: start
2023-04-18 21:07:06 18984-19014 Worker1 D doWork: end
2023-04-18 21:07:06 18984-19016 Worker3 D doWork: end
2023-04-18 21:07:06 18984-19015 Worker2 D doWork: end
The tasks started at 21:07:05
and were executed in parallel in different threads, and each was completed on its own time.
We saw parallel execution. Now let's execute them sequentially. We pass the first task to the beginWith
method and thereby create the beginning of the task sequence. Next, by calling the then
method, we add the second and third tasks to the sequence. And using the enqueue
method, we send this sequence to launch.
Result:
2023-04-18 21:28:59 20007-20036 Worker1 D doWork: start
2023-04-18 21:29:00 20007-20036 Worker1 D doWork: end
2023-04-18 21:29:00 20007-20044 Worker2 D doWork: start
2023-04-18 21:29:01 20007-20044 Worker2 D doWork: end
2023-04-18 21:29:01 20007-20045 Worker3 D doWork: start
2023-04-18 21:29:02 20007-20045 Worker3 D doWork: end
The logs show that the tasks were performed one after another, and in the exact sequence that we indicated.
A task that cannot currently be started will wait. And, accordingly, all other tasks that are in the sequence after this task will also wait.
Let's look at an example.
Let the second task have a criterion - the presence of the Internet. Turn off the Internet on the device and start the sequence. The first task doesn't care; it's running. The turn of the second task comes, but there is no Internet; so the second task is put on hold, and the third task can be started only after the completion of the second one. So it has to wait. We turn on the Internet, the second task is performed, and after it, the third one is performed.
If any task in the sequence ends with a FAILURE status, the entire chain will be stopped.
We can combine serial and parallel execution of tasks.
WorkManager
.getInstance(this)
.beginWith(listOf(worker1, worker2))
.then(listOf(worker3, worker4))
.then(worker5)
.enqueue()
Here we form a sequence, but at the same time, we specify two tasks for the first (beginWith) and second (first then) step of the sequence.
As a result, the tasks worker1
and worker2
will be executed first, and they will be executed in parallel. After that, worker3
and worker4
will be executed, also in parallel to each other. And after that - worker5
.
In the logs, it will look like this:
2023-04-18 21:41:31 20434-20464 Worker1 D doWork: start
2023-04-18 21:41:31 20434-20465 Worker2 D doWork: start
2023-04-18 21:41:32 20434-20464 Worker1 D doWork: end
2023-04-18 21:41:32 20434-20465 Worker2 D doWork: end
2023-04-18 21:41:32 20434-20476 Worker3 D doWork: start
2023-04-18 21:41:32 20434-20464 Worker4 D doWork: start
2023-04-18 21:41:33 20434-20476 Worker3 D doWork: end
2023-04-18 21:41:33 20434-20464 Worker4 D doWork: end
2023-04-18 21:41:33 20434-20465 Worker5 D doWork: start
2023-04-18 21:41:34 20434-20465 Worker5 D doWork: end
The first and second tasks start at the same time. When they are both completed, the third and fourth start, also at the same time. When they are both completed, the fifth task starts.
Let's consider another case. Suppose we need the second task to be completed after the first, and the fourth after the third. We have two sequences and they can be run in parallel. And when these two sequences are completed, you need to start the fifth task.
This is how it is done:
val chain12: WorkContinuation = WorkManager
.getInstance(this)
.beginWith(worker1)
.then(worker2)
val chain34: WorkContinuation = WorkManager
.getInstance(this)
.beginWith(worker3)
.then(worker4)
WorkContinuation.combine(listOf(chain12, chain34))
.then(worker5)
.enqueue()
WorkContinuation
is a sequence of tasks. We create a chain12
sequence consisting of the first and second tasks, and a chain34
sequence consisting of the third and fourth tasks. To make these sequences run in parallel to each other, we pass them to the combine
method. Then we pass the fifth task to the then
method, which starts after all the sequences from combine
are completed.
Result:
2023-04-19 15:58:20 1511-1534 Worker1 D doWork: start
2023-04-19 15:58:20 1511-1535 Worker3 D doWork: start
2023-04-19 15:58:21 1511-1534 Worker1 D doWork: end
2023-04-19 15:58:21 1511-1535 Worker3 D doWork: end
2023-04-19 15:58:21 1511-1571 Worker2 D doWork: start
2023-04-19 15:58:21 1511-1534 Worker4 D doWork: start
2023-04-19 15:58:22 1511-1571 Worker2 D doWork: end
2023-04-19 15:58:22 1511-1534 Worker4 D doWork: end
2023-04-19 15:58:22 1511-1571 Worker5 D doWork: start
2023-04-19 15:58:23 1511-1571 Worker5 D doWork: end
The first and third tasks start, i.e. sequences start running in parallel. When both sequences are completed, the fifth task starts.
We can make the task sequence unique. To do this, we start the sequence using the beginUniqueWork method.
private fun work() {
val worker1 = OneTimeWorkRequestBuilder<Worker1>()
.build()
val worker3 = OneTimeWorkRequestBuilder<Worker3>()
.build()
val worker5 = OneTimeWorkRequestBuilder<Worker5>()
.build()
WorkManager.getInstance(this)
.beginUniqueWork("work123", ExistingWorkPolicy.REPLACE, worker1)
.then(worker3)
.then(worker5)
.enqueue()
}
findViewById<AppCompatButton>(R.id.startWork).setOnClickListener {
Log.e(TAG, "enqueue REPLACE")
work()
}
Specify the name of the sequence, the mode, and the first task of the sequence.
We specified REPLACE as the mode. This means that if a sequence with the same name is already running, then another run will cause the currently running sequence to be stopped and a new one started.
I added logging to the call to the enqueue
method, which starts the sequence. Let's see what's happening in the logs.
2023-04-19 16:52:32 17809-17809 WorkManagerActivity3 E enqueue REPLACE
2023-04-19 16:52:33 17809-17882 Worker1 D doWork: start
2023-04-19 16:52:38 17809-17882 Worker1 D doWork: end
2023-04-19 16:52:38 17809-17914 Worker3 D doWork: start
2023-04-19 16:52:43 17809-17914 Worker3 D doWork: end
2023-04-19 16:52:43 17809-17952 Worker5 D doWork: start
2023-04-19 16:52:46 17809-17809 WorkManagerActivity3 E enqueue REPLACE
2023-04-19 16:52:46 17809-17880 Worker5 D onStopped
2023-04-19 16:52:46 17809-17882 Worker1 D doWork: start
2023-04-19 16:52:48 17809-17952 Worker5 D doWork: end
2023-04-19 16:52:51 17809-17882 Worker1 D doWork: end
2023-04-19 16:52:51 17809-17914 Worker3 D doWork: start
2023-04-19 16:52:56 17809-17914 Worker3 D doWork: end
2023-04-19 16:52:56 17809-17952 Worker5 D doWork: start
2023-04-19 16:53:01 17809-17952 Worker5 D doWork: end
16:52:32 is the first run of the sequence. Tasks start running one after another.
16:52:46 - while MyWorker5 is running, I create and start the same sequence with the same name - work123. The currently running sequence stops and a new one starts.
The KEEP mode will keep the currently executing sequence running. The new one will be ignored.
Code:
WorkManager.getInstance(this)
.beginUniqueWork("work123", ExistingWorkPolicy.KEEP, worker1)
.then(worker3)
.then(worker5)
.enqueue()
Logs:
2023-04-19 17:05:17 20030-20030 WorkManagerActivity3 E enqueue KEEP
2023-04-19 17:05:18 20030-20207 Worker1 D doWork: start
2023-04-19 17:05:23 20030-20207 Worker1 D doWork: end
2023-04-19 17:05:23 20030-20227 Worker3 D doWork: start
2023-04-19 17:05:28 20030-20227 Worker3 D doWork: end
2023-04-19 17:05:28 20030-20272 Worker5 D doWork: start
2023-04-19 17:05:29 20030-20030 WorkManagerActivity3 E enqueue KEEP
2023-04-19 17:05:33 20030-20272 Worker5 D doWork: end
17:05:29 - I tried to run the sequence again, but was ignored because there is already a sequence with the same name in the works.
APPEND mode will start a new sequence after executing the current one.
Code:
WorkManager.getInstance(this)
.beginUniqueWork("work123", ExistingWorkPolicy.APPEND, worker1)
.then(worker3)
.then(worker5)
.enqueue()
Logs:
2023-04-19 17:10:28 21636-21636 WorkManagerActivity3 E enqueue APPEND
2023-04-19 17:10:28 21636-21730 Worker1 D doWork: start
2023-04-19 17:10:33 21636-21730 Worker1 D doWork: end
2023-04-19 17:10:33 21636-21784 Worker3 D doWork: start
2023-04-19 17:10:38 21636-21784 Worker3 D doWork: end
2023-04-19 17:10:38 21636-21836 Worker5 D doWork: start
2023-04-19 17:10:39 21636-21636 WorkManagerActivity3 E enqueue APPEND
2023-04-19 17:10:43 21636-21836 Worker5 D doWork: end
2023-04-19 17:10:43 21636-21730 Worker1 D doWork: start
2023-04-19 17:10:48 21636-21730 Worker1 D doWork: end
2023-04-19 17:10:48 21636-21784 Worker3 D doWork: start
2023-04-19 17:10:53 21636-21784 Worker3 D doWork: end
2023-04-19 17:10:53 21636-21836 Worker5 D doWork: start
2023-04-19 17:10:58 21636-21836 Worker5 D doWork: end
17:10:39 - the current sequence was not interrupted, and a new one was launched immediately after the end of the current one.
Be careful with this mode, because. an error in the current sequence may result in the new sequence not starting.
I created and restarted the same sequence in these last examples, but the current and new sequences may consist of different tasks. The main thing here is the same name as the sequences.