Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 1 | # Threading and Tasks in Chrome - FAQ |
| 2 | |
| 3 | [TOC] |
| 4 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 5 | Note: Make sure to read the main [Threading and Tasks](threading_and_tasks.md) |
| 6 | docs first. |
| 7 | |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 8 | ## General |
| 9 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 10 | ### On which thread will a task run? |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 11 | |
Gabriel Charette | 9b6c0407 | 2022-04-01 23:22:46 | [diff] [blame] | 12 | A task is posted through the `base/task/thread_pool.h` API with `TaskTraits`. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 13 | |
| 14 | * If `TaskTraits` contain `BrowserThread::UI`: |
| 15 | * The task runs on the main thread. |
| 16 | |
| 17 | * If `TaskTraits` contain `BrowserThread::IO`: |
| 18 | * The task runs on the IO thread. |
| 19 | |
| 20 | * If `TaskTraits` don't contain `BrowserThread::UI/IO`: |
| 21 | * If the task is posted through a `SingleThreadTaskRunner` obtained from |
Sami Kyostila | 831c60b | 2019-07-31 13:31:23 | [diff] [blame] | 22 | `CreateSingleThreadTaskRunner(..., mode)`: |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 23 | * Where `mode` is `SingleThreadTaskRunnerThreadMode::DEDICATED`: |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 24 | * The task runs on a thread that only runs tasks from that |
| 25 | SingleThreadTaskRunner. This is not the main thread nor the IO |
| 26 | thread. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 27 | |
| 28 | * Where `mode` is `SingleThreadTaskRunnerThreadMode::SHARED`: |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 29 | * The task runs on a thread that runs tasks from one or many |
| 30 | unrelated SingleThreadTaskRunners. This is not the main thread nor |
| 31 | the IO thread. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 32 | |
| 33 | * Otherwise: |
| 34 | * The task runs in a thread pool. |
| 35 | |
| 36 | As explained in [Prefer Sequences to Threads](threading_and_tasks.md#Prefer-Sequences-to-Threads), |
| 37 | tasks should generally run on a sequence in a thread pool rather than on a |
| 38 | dedicated thread. |
| 39 | |
Francois Doray | 571f85a9 | 2019-02-01 17:02:16 | [diff] [blame] | 40 | ### Does release of a TaskRunner block on posted tasks? |
| 41 | |
| 42 | Releasing a TaskRunner reference does not wait for tasks previously posted to |
| 43 | the TaskRunner to complete their execution. Tasks can run normally after the |
| 44 | last client reference to the TaskRunner to which they were posted has been |
| 45 | released and it can even be kept alive indefinitely through |
Sean Maher | 70f294293 | 2023-01-04 22:15:06 | [diff] [blame] | 46 | `SequencedTaskRunner::GetCurrentDefault()` or |
| 47 | `SingleThreadTaskRunner::GetCurrentDefault()`. |
Francois Doray | 571f85a9 | 2019-02-01 17:02:16 | [diff] [blame] | 48 | |
| 49 | If you want some state to be deleted only after all tasks currently posted to a |
| 50 | SequencedTaskRunner have run, store that state in a helper object and schedule |
| 51 | deletion of that helper object on the SequencedTaskRunner using |
| 52 | `base::OnTaskRunnerDeleter` after posting the last task. See |
| 53 | [example CL](https://crrev.com/c/1416271/15/chrome/browser/performance_monitor/system_monitor.h). |
| 54 | But be aware that any task posting back to its "current" sequence can enqueue |
| 55 | itself after that "last" task. |
| 56 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 57 | ## Making blocking calls (which do not use the CPU) |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 58 | |
Etienne Pierre-doray | d710e152 | 2018-10-26 16:22:23 | [diff] [blame] | 59 | ### How to make a blocking call without preventing other tasks from being scheduled? |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 60 | |
Etienne Pierre-doray | d710e152 | 2018-10-26 16:22:23 | [diff] [blame] | 61 | The steps depend on where the task runs (see [Where will a task run?](#On-what-thread-will-a-task-run_)). |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 62 | |
| 63 | If the task runs in a thread pool: |
| 64 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 65 | * Annotate the scope that may block with |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 66 | `ScopedBlockingCall(BlockingType::MAY_BLOCK/WILL_BLOCK)`. A few milliseconds |
| 67 | after the annotated scope is entered, the capacity of the thread pool is |
| 68 | incremented. This ensures that your task doesn't reduce the number of tasks |
| 69 | that can run concurrently on the CPU. If the scope exits, the thread pool |
Etienne Pierre-doray | d710e152 | 2018-10-26 16:22:23 | [diff] [blame] | 70 | capacity goes back to normal. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 71 | |
| 72 | If the task runs on the main thread, the IO thread or a `SHARED |
| 73 | SingleThreadTaskRunner`: |
| 74 | |
| 75 | * Blocking on one of these threads will cause breakages. Move your task to a |
| 76 | thread pool (or to a `DEDICATED SingleThreadTaskRunner` if necessary - see |
| 77 | [Prefer Sequences to Threads](threading_and_tasks.md#Prefer-Sequences-to-Threads)). |
| 78 | |
| 79 | If the task runs on a `DEDICATED SingleThreadTaskRunner`: |
| 80 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 81 | * Annotate the scope that may block with |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 82 | `ScopedBlockingCall(BlockingType::MAY_BLOCK/WILL_BLOCK)`. The annotation is a |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 83 | no-op that documents the blocking behavior (and makes it pass assertions). |
| 84 | Tasks posted to the same `DEDICATED SingleThreadTaskRunner` won't run until |
| 85 | your blocking task returns (they will never run if the blocking task never |
| 86 | returns). |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 87 | |
Etienne Pierre-doray | 338d61c | 2018-10-16 12:35:32 | [diff] [blame] | 88 | [base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h) |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 89 | explains the difference between `MAY_BLOCK` and `WILL_BLOCK` and gives |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 90 | examples of blocking operations. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 91 | |
Etienne Pierre-doray | d710e152 | 2018-10-26 16:22:23 | [diff] [blame] | 92 | ### How to make a blocking call that may never return without preventing other tasks from being scheduled? |
| 93 | |
| 94 | If you can't avoid making a call to a third-party library that may block off- |
| 95 | CPU, follow recommendations in [How to make a blocking call without affecting |
| 96 | other tasks?](#How-to-make-a-blocking-call-without-affecting-other-tasks_). |
| 97 | This ensures that a current task doesn't prevent other tasks from running even |
| 98 | if it never returns. |
| 99 | |
| 100 | Since tasks posted to the same sequence can't run concurrently, it is advisable |
| 101 | to run tasks that may block indefinitely in |
| 102 | [parallel](threading_and_tasks.md#posting-a-parallel-task) rather than in |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 103 | [sequence](threading_and_tasks.md#posting-a-sequenced-task) (unless posting many |
| 104 | such tasks at which point sequencing can be a useful tool to prevent flooding). |
Etienne Pierre-doray | d710e152 | 2018-10-26 16:22:23 | [diff] [blame] | 105 | |
Etienne Pierre-doray | 338d61c | 2018-10-16 12:35:32 | [diff] [blame] | 106 | ### Do calls to blocking //base APIs need to be annotated with ScopedBlockingCall? |
| 107 | |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 108 | No. All blocking //base APIs (e.g. `base::ReadFileToString`, `base::File::Read`, |
| 109 | `base::SysInfo::AmountOfFreeDiskSpace`, `base::WaitableEvent::Wait`, etc.) have |
| 110 | their own internal annotations. See |
Etienne Pierre-doray | 338d61c | 2018-10-16 12:35:32 | [diff] [blame] | 111 | [base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h). |
| 112 | |
| 113 | ### Can multiple ScopedBlockingCall be nested for the purpose of documentation? |
| 114 | |
| 115 | Nested `ScopedBlockingCall` are supported. Most of the time, the inner |
Jared Saul | ea867ab | 2021-07-15 17:39:01 | [diff] [blame] | 116 | ScopedBlockingCalls will no-op (the exception is `WILL_BLOCK` nested in `MAY_BLOCK`). |
Etienne Pierre-doray | 338d61c | 2018-10-16 12:35:32 | [diff] [blame] | 117 | As such, it is permitted to add a ScopedBlockingCall in the scope where a function |
| 118 | that is already annotated is called for documentation purposes.: |
| 119 | |
| 120 | ```cpp |
| 121 | Data GetDataFromNetwork() { |
| 122 | ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK); |
| 123 | // Fetch data from network. |
| 124 | ... |
| 125 | return data; |
| 126 | } |
| 127 | |
| 128 | void ProcessDataFromNetwork() { |
| 129 | Data data; |
| 130 | { |
| 131 | // Document the blocking behavior with a ScopedBlockingCall. |
| 132 | // Permitted, but not required since GetDataFromNetwork() is itself annotated. |
| 133 | ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK); |
| 134 | data = GetDataFromNetwork(); |
| 135 | } |
| 136 | CPUIntensiveProcessing(data); |
| 137 | } |
| 138 | ``` |
| 139 | |
| 140 | However, CPU usage should always be minimal within the scope of |
| 141 | `ScopedBlockingCall`. See |
| 142 | [base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h). |
| 143 | |
| 144 | |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 145 | ## Sequences |
| 146 | |
| 147 | ### How to migrate from SingleThreadTaskRunner to SequencedTaskRunner? |
| 148 | |
| 149 | The following mappings can be useful when migrating code from a |
| 150 | `SingleThreadTaskRunner` to a `SequencedTaskRunner`: |
| 151 | |
| 152 | * base::SingleThreadTaskRunner -> base::SequencedTaskRunner |
| 153 | * SingleThreadTaskRunner::BelongsToCurrentThread() -> SequencedTaskRunner::RunsTasksInCurrentSequence() |
Sean Maher | 70f294293 | 2023-01-04 22:15:06 | [diff] [blame] | 154 | * base::SingleThreadTaskRunner::CurrentDefaultHandle -> |
| 155 | base::SequencedTaskRunnerHandle::CurrentDefaultHandle |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 156 | * THREAD_CHECKER -> SEQUENCE_CHECKER |
| 157 | * base::ThreadLocalStorage::Slot -> base::SequenceLocalStorageSlot |
| 158 | * BrowserThread::DeleteOnThread -> base::OnTaskRunnerDeleter / base::RefCountedDeleteOnSequence |
| 159 | * BrowserMessageFilter::OverrideThreadForMessage() -> BrowserMessageFilter::OverrideTaskRunnerForMessage() |
Sami Kyostila | 831c60b | 2019-07-31 13:31:23 | [diff] [blame] | 160 | * CreateSingleThreadTaskRunner() -> CreateSequencedTaskRunner() |
| 161 | * Every CreateSingleThreadTaskRunner() usage, outside of |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 162 | BrowserThread::UI/IO, should be accompanied with a comment and ideally a |
| 163 | bug to make it sequence when the sequence-unfriendly dependency is |
| 164 | addressed. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 165 | |
| 166 | ### How to ensure mutual exclusion between tasks posted by a component? |
| 167 | |
Sami Kyostila | 831c60b | 2019-07-31 13:31:23 | [diff] [blame] | 168 | Create a `SequencedTaskRunner` using `CreateSequencedTaskRunner()` and |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 169 | store it on an object that can be accessed from all the PostTask() call sites |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 170 | that require mutual exclusion. If there isn't a shared object that can own a |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 171 | common `SequencedTaskRunner`, use |
| 172 | `Lazy(Sequenced|SingleThread|COMSTA)TaskRunner` in an anonymous namespace. |
| 173 | |
| 174 | ## Tests |
| 175 | |
| 176 | ### How to test code that posts tasks? |
| 177 | |
| 178 | If the test uses `BrowserThread::UI/IO`, instantiate a |
Gabriel Charette | 798fde7 | 2019-08-20 22:24:04 | [diff] [blame] | 179 | `content::BrowserTaskEnvironment` for the scope of the test. Call |
| 180 | `BrowserTaskEnvironment::RunUntilIdle()` to wait until all tasks have run. |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 181 | |
| 182 | If the test doesn't use `BrowserThread::UI/IO`, instantiate a |
Gabriel Charette | 694c3c33 | 2019-08-19 14:53:05 | [diff] [blame] | 183 | `base::test::TaskEnvironment` for the scope of the test. Call |
| 184 | `base::test::TaskEnvironment::RunUntilIdle()` to wait until all tasks have |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 185 | run. |
| 186 | |
| 187 | In both cases, you can run tasks until a condition is met. A test that waits for |
| 188 | a condition to be met is easier to understand and debug than a test that waits |
| 189 | for all tasks to run. |
| 190 | |
| 191 | ```cpp |
| 192 | int g_condition = false; |
| 193 | |
| 194 | base::RunLoop run_loop; |
Gabriel Charette | 1f57c9b8 | 2022-03-16 22:54:34 | [diff] [blame] | 195 | base::ThreadPool::PostTask(FROM_HERE, {}, base::BindOnce( |
Thiabaud Engelbrecht | c0a197e6 | 2025-05-14 18:53:45 | [diff] [blame] | 196 | [] (base::OnceClosure quit_closure) { |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 197 | g_condition = true; |
| 198 | std::move(quit_closure).Run(); |
| 199 | }, run_loop.QuitClosure())); |
| 200 | |
| 201 | // Runs tasks until the quit closure is invoked. |
| 202 | run_loop.Run(); |
| 203 | |
| 204 | EXPECT_TRUE(g_condition); |
| 205 | ``` |
| 206 | |
| 207 | ## Your question hasn't been answered? |
| 208 | |
Gabriel Charette | 8917f4c | 2018-11-22 15:50:28 | [diff] [blame] | 209 | 1. Check the main [Threading and Tasks](threading_and_tasks.md) docs. |
| 210 | 2. Ping |
Francois Doray | cc49b74d | 2018-10-09 13:49:09 | [diff] [blame] | 211 | [scheduler-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/scheduler-dev). |