| // 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); |
| } |
| } |
| } |