1 // Copyright 2017 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.task; 6 7 import android.util.Pair; 8 9 import org.chromium.base.TraceEvent; 10 11 import java.util.LinkedList; 12 13 import javax.annotation.concurrent.GuardedBy; 14 15 /** 16 * Allows chaining multiple tasks on arbitrary threads, with the next task posted when one 17 * completes. 18 * 19 * How this differs from SequencedTaskRunner: 20 * Deferred posting of subsequent tasks allows more time for Android framework tasks to run 21 * (e.g. input events). As such, this class really only makes sense when submitting tasks to 22 * SingleThreadTaskRunners. 23 * 24 * Threading: 25 * - This class is threadsafe and all methods may be called from any thread. 26 * - Tasks may run with arbitrary TaskTraits, unless tasks are coalesced, in which case all tasks 27 * must run on the same thread. 28 */ 29 public class ChainedTasks { 30 private final LinkedList<Pair<Integer, Runnable>> mTasks = new LinkedList<>(); 31 32 @GuardedBy("mTasks") 33 private boolean mFinalized; 34 35 private volatile boolean mCanceled; 36 private int mIterationIdForTesting = PostTask.sTestIterationForTesting; 37 38 private final Runnable mRunAndPost = 39 new Runnable() { 40 @Override 41 @SuppressWarnings("NoDynamicStringsInTraceEventCheck") 42 public void run() { 43 if (mIterationIdForTesting != PostTask.sTestIterationForTesting) { 44 cancel(); 45 } 46 if (mCanceled) return; 47 48 Pair<Integer, Runnable> pair = mTasks.pop(); 49 try (TraceEvent e = 50 TraceEvent.scoped( 51 "ChainedTask.run: " + pair.second.getClass().getName())) { 52 pair.second.run(); 53 } 54 if (!mTasks.isEmpty()) PostTask.postTask(mTasks.peek().first, this); 55 } 56 }; 57 58 /** 59 * Adds a task to the list of tasks to run. Cannot be called once {@link start()} has been 60 * called. 61 */ add(@askTraits int traits, Runnable task)62 public void add(@TaskTraits int traits, Runnable task) { 63 assert mIterationIdForTesting == PostTask.sTestIterationForTesting; 64 65 synchronized (mTasks) { 66 assert !mFinalized : "Must not call add() after start()"; 67 mTasks.add(new Pair<>(traits, task)); 68 } 69 } 70 71 /** Cancels the remaining tasks. */ cancel()72 public void cancel() { 73 synchronized (mTasks) { 74 mFinalized = true; 75 mCanceled = true; 76 } 77 } 78 79 /** 80 * Posts or runs all the tasks, one by one. 81 * 82 * @param coalesceTasks if false, posts the tasks. Otherwise run them in a single task. If 83 * called on the thread matching the TaskTraits, will block and run all tasks synchronously. 84 */ start(final boolean coalesceTasks)85 public void start(final boolean coalesceTasks) { 86 synchronized (mTasks) { 87 assert !mFinalized : "Cannot call start() several times"; 88 mFinalized = true; 89 } 90 if (mTasks.isEmpty()) return; 91 if (coalesceTasks) { 92 @TaskTraits int traits = mTasks.peek().first; 93 PostTask.runOrPostTask( 94 traits, 95 () -> { 96 for (Pair<Integer, Runnable> pair : mTasks) { 97 assert PostTask.canRunTaskImmediately(pair.first); 98 pair.second.run(); 99 if (mCanceled) return; 100 } 101 }); 102 } else { 103 PostTask.postTask(mTasks.peek().first, mRunAndPost); 104 } 105 } 106 } 107