blob: 1a9dd302166b2f1f6a67843e61decdfb5328b6e0 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.task;
import android.util.Pair;
import org.chromium.base.TraceEvent;
import java.util.LinkedList;
import javax.annotation.concurrent.GuardedBy;
/**
* Allows chaining multiple tasks on arbitrary threads, with the next task posted when one
* completes.
*
* How this differs from SequencedTaskRunner:
* Deferred posting of subsequent tasks allows more time for Android framework tasks to run
* (e.g. input events). As such, this class really only makes sense when submitting tasks to
* SingleThreadTaskRunners.
*
* Threading:
* - This class is threadsafe and all methods may be called from any thread.
* - Tasks may run with arbitrary TaskTraits, unless tasks are coalesced, in which case all tasks
* must run on the same thread.
*/
public class ChainedTasks {
private final LinkedList<Pair<Integer, Runnable>> mTasks = new LinkedList<>();
@GuardedBy("mTasks")
private boolean mFinalized;
private volatile boolean mCanceled;
private int mIterationIdForTesting = PostTask.sTestIterationForTesting;
private final Runnable mRunAndPost =
new Runnable() {
@Override
@SuppressWarnings("NoDynamicStringsInTraceEventCheck")
public void run() {
if (mIterationIdForTesting != PostTask.sTestIterationForTesting) {
cancel();
}
if (mCanceled) return;
Pair<Integer, Runnable> pair = mTasks.pop();
try (TraceEvent e =
TraceEvent.scoped(
"ChainedTask.run: " + pair.second.getClass().getName())) {
pair.second.run();
}
if (!mTasks.isEmpty()) PostTask.postTask(mTasks.peek().first, this);
}
};
/**
* Adds a task to the list of tasks to run. Cannot be called once {@link start()} has been
* called.
*/
public void add(@TaskTraits int traits, Runnable task) {
assert mIterationIdForTesting == PostTask.sTestIterationForTesting;
synchronized (mTasks) {
assert !mFinalized : "Must not call add() after start()";
mTasks.add(new Pair<>(traits, task));
}
}
/** Cancels the remaining tasks. */
public void cancel() {
synchronized (mTasks) {
mFinalized = true;
mCanceled = true;
}
}
/**
* Posts or runs all the tasks, one by one.
*
* @param coalesceTasks if false, posts the tasks. Otherwise run them in a single task. If
* called on the thread matching the TaskTraits, will block and run all tasks synchronously.
*/
public void start(final boolean coalesceTasks) {
synchronized (mTasks) {
assert !mFinalized : "Cannot call start() several times";
mFinalized = true;
}
if (mTasks.isEmpty()) return;
if (coalesceTasks) {
@TaskTraits int traits = mTasks.peek().first;
PostTask.runOrPostTask(
traits,
() -> {
for (Pair<Integer, Runnable> pair : mTasks) {
assert PostTask.canRunTaskImmediately(pair.first);
pair.second.run();
if (mCanceled) return;
}
});
} else {
PostTask.postTask(mTasks.peek().first, mRunAndPost);
}
}
}