
This CL contains: - removal of references to task runner handles in comments - removal of nearly all calls, includes, and instantiations of thread and sequenced task runner handles Each of these were replaced with references to the new api: TaskRunner::CurrentDefaultHandle and associated methods. After this change, all that should be left to do for this method refactor is the deletion of the old API. A few of the changes were done with a script, but they were manually audited. Bug: 1026641 Ax-Relnotes: n/a Change-Id: I0858dccac95b485d982aa5152c6b461c4ce05aa4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4117257 Commit-Queue: Gabriel Charette <gab@chromium.org> Owners-Override: Gabriel Charette <gab@chromium.org> Reviewed-by: Gabriel Charette <gab@chromium.org> Commit-Queue: Sean Maher <spvw@chromium.org> Cr-Commit-Position: refs/heads/main@{#1088971}
212 lines
8.7 KiB
Markdown
212 lines
8.7 KiB
Markdown
# Threading and Tasks in Chrome - FAQ
|
|
|
|
[TOC]
|
|
|
|
Note: Make sure to read the main [Threading and Tasks](threading_and_tasks.md)
|
|
docs first.
|
|
|
|
## General
|
|
|
|
### On which thread will a task run?
|
|
|
|
A task is posted through the `base/task/thread_pool.h` API with `TaskTraits`.
|
|
|
|
* If `TaskTraits` contain `BrowserThread::UI`:
|
|
* The task runs on the main thread.
|
|
|
|
* If `TaskTraits` contain `BrowserThread::IO`:
|
|
* The task runs on the IO thread.
|
|
|
|
* If `TaskTraits` don't contain `BrowserThread::UI/IO`:
|
|
* If the task is posted through a `SingleThreadTaskRunner` obtained from
|
|
`CreateSingleThreadTaskRunner(..., mode)`:
|
|
* Where `mode` is `SingleThreadTaskRunnerThreadMode::DEDICATED`:
|
|
* The task runs on a thread that only runs tasks from that
|
|
SingleThreadTaskRunner. This is not the main thread nor the IO
|
|
thread.
|
|
|
|
* Where `mode` is `SingleThreadTaskRunnerThreadMode::SHARED`:
|
|
* The task runs on a thread that runs tasks from one or many
|
|
unrelated SingleThreadTaskRunners. This is not the main thread nor
|
|
the IO thread.
|
|
|
|
* Otherwise:
|
|
* The task runs in a thread pool.
|
|
|
|
As explained in [Prefer Sequences to Threads](threading_and_tasks.md#Prefer-Sequences-to-Threads),
|
|
tasks should generally run on a sequence in a thread pool rather than on a
|
|
dedicated thread.
|
|
|
|
### Does release of a TaskRunner block on posted tasks?
|
|
|
|
Releasing a TaskRunner reference does not wait for tasks previously posted to
|
|
the TaskRunner to complete their execution. Tasks can run normally after the
|
|
last client reference to the TaskRunner to which they were posted has been
|
|
released and it can even be kept alive indefinitely through
|
|
`SequencedTaskRunner::GetCurrentDefault()` or
|
|
`SingleThreadTaskRunner::GetCurrentDefault()`.
|
|
|
|
If you want some state to be deleted only after all tasks currently posted to a
|
|
SequencedTaskRunner have run, store that state in a helper object and schedule
|
|
deletion of that helper object on the SequencedTaskRunner using
|
|
`base::OnTaskRunnerDeleter` after posting the last task. See
|
|
[example CL](https://crrev.com/c/1416271/15/chrome/browser/performance_monitor/system_monitor.h).
|
|
But be aware that any task posting back to its "current" sequence can enqueue
|
|
itself after that "last" task.
|
|
|
|
## Making blocking calls (which do not use the CPU)
|
|
|
|
### How to make a blocking call without preventing other tasks from being scheduled?
|
|
|
|
The steps depend on where the task runs (see [Where will a task run?](#On-what-thread-will-a-task-run_)).
|
|
|
|
If the task runs in a thread pool:
|
|
|
|
* Annotate the scope that may block with
|
|
`ScopedBlockingCall(BlockingType::MAY_BLOCK/WILL_BLOCK)`. A few milliseconds
|
|
after the annotated scope is entered, the capacity of the thread pool is
|
|
incremented. This ensures that your task doesn't reduce the number of tasks
|
|
that can run concurrently on the CPU. If the scope exits, the thread pool
|
|
capacity goes back to normal.
|
|
|
|
If the task runs on the main thread, the IO thread or a `SHARED
|
|
SingleThreadTaskRunner`:
|
|
|
|
* Blocking on one of these threads will cause breakages. Move your task to a
|
|
thread pool (or to a `DEDICATED SingleThreadTaskRunner` if necessary - see
|
|
[Prefer Sequences to Threads](threading_and_tasks.md#Prefer-Sequences-to-Threads)).
|
|
|
|
If the task runs on a `DEDICATED SingleThreadTaskRunner`:
|
|
|
|
* Annotate the scope that may block with
|
|
`ScopedBlockingCall(BlockingType::MAY_BLOCK/WILL_BLOCK)`. The annotation is a
|
|
no-op that documents the blocking behavior (and makes it pass assertions).
|
|
Tasks posted to the same `DEDICATED SingleThreadTaskRunner` won't run until
|
|
your blocking task returns (they will never run if the blocking task never
|
|
returns).
|
|
|
|
[base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h)
|
|
explains the difference between `MAY_BLOCK` and `WILL_BLOCK` and gives
|
|
examples of blocking operations.
|
|
|
|
### How to make a blocking call that may never return without preventing other tasks from being scheduled?
|
|
|
|
If you can't avoid making a call to a third-party library that may block off-
|
|
CPU, follow recommendations in [How to make a blocking call without affecting
|
|
other tasks?](#How-to-make-a-blocking-call-without-affecting-other-tasks_).
|
|
This ensures that a current task doesn't prevent other tasks from running even
|
|
if it never returns.
|
|
|
|
Since tasks posted to the same sequence can't run concurrently, it is advisable
|
|
to run tasks that may block indefinitely in
|
|
[parallel](threading_and_tasks.md#posting-a-parallel-task) rather than in
|
|
[sequence](threading_and_tasks.md#posting-a-sequenced-task) (unless posting many
|
|
such tasks at which point sequencing can be a useful tool to prevent flooding).
|
|
|
|
### Do calls to blocking //base APIs need to be annotated with ScopedBlockingCall?
|
|
|
|
No. All blocking //base APIs (e.g. `base::ReadFileToString`, `base::File::Read`,
|
|
`base::SysInfo::AmountOfFreeDiskSpace`, `base::WaitableEvent::Wait`, etc.) have
|
|
their own internal annotations. See
|
|
[base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h).
|
|
|
|
### Can multiple ScopedBlockingCall be nested for the purpose of documentation?
|
|
|
|
Nested `ScopedBlockingCall` are supported. Most of the time, the inner
|
|
ScopedBlockingCalls will no-op (the exception is `WILL_BLOCK` nested in `MAY_BLOCK`).
|
|
As such, it is permitted to add a ScopedBlockingCall in the scope where a function
|
|
that is already annotated is called for documentation purposes.:
|
|
|
|
```cpp
|
|
Data GetDataFromNetwork() {
|
|
ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
|
|
// Fetch data from network.
|
|
...
|
|
return data;
|
|
}
|
|
|
|
void ProcessDataFromNetwork() {
|
|
Data data;
|
|
{
|
|
// Document the blocking behavior with a ScopedBlockingCall.
|
|
// Permitted, but not required since GetDataFromNetwork() is itself annotated.
|
|
ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
|
|
data = GetDataFromNetwork();
|
|
}
|
|
CPUIntensiveProcessing(data);
|
|
}
|
|
```
|
|
|
|
However, CPU usage should always be minimal within the scope of
|
|
`ScopedBlockingCall`. See
|
|
[base/threading/scoped_blocking_call.h](https://cs.chromium.org/chromium/src/base/threading/scoped_blocking_call.h).
|
|
|
|
|
|
## Sequences
|
|
|
|
### How to migrate from SingleThreadTaskRunner to SequencedTaskRunner?
|
|
|
|
The following mappings can be useful when migrating code from a
|
|
`SingleThreadTaskRunner` to a `SequencedTaskRunner`:
|
|
|
|
* base::SingleThreadTaskRunner -> base::SequencedTaskRunner
|
|
* SingleThreadTaskRunner::BelongsToCurrentThread() -> SequencedTaskRunner::RunsTasksInCurrentSequence()
|
|
* base::SingleThreadTaskRunner::CurrentDefaultHandle ->
|
|
base::SequencedTaskRunnerHandle::CurrentDefaultHandle
|
|
* THREAD_CHECKER -> SEQUENCE_CHECKER
|
|
* base::ThreadLocalStorage::Slot -> base::SequenceLocalStorageSlot
|
|
* BrowserThread::DeleteOnThread -> base::OnTaskRunnerDeleter / base::RefCountedDeleteOnSequence
|
|
* BrowserMessageFilter::OverrideThreadForMessage() -> BrowserMessageFilter::OverrideTaskRunnerForMessage()
|
|
* CreateSingleThreadTaskRunner() -> CreateSequencedTaskRunner()
|
|
* Every CreateSingleThreadTaskRunner() usage, outside of
|
|
BrowserThread::UI/IO, should be accompanied with a comment and ideally a
|
|
bug to make it sequence when the sequence-unfriendly dependency is
|
|
addressed.
|
|
|
|
### How to ensure mutual exclusion between tasks posted by a component?
|
|
|
|
Create a `SequencedTaskRunner` using `CreateSequencedTaskRunner()` and
|
|
store it on an object that can be accessed from all the PostTask() call sites
|
|
that require mutual exclusion. If there isn't a shared object that can own a
|
|
common `SequencedTaskRunner`, use
|
|
`Lazy(Sequenced|SingleThread|COMSTA)TaskRunner` in an anonymous namespace.
|
|
|
|
## Tests
|
|
|
|
### How to test code that posts tasks?
|
|
|
|
If the test uses `BrowserThread::UI/IO`, instantiate a
|
|
`content::BrowserTaskEnvironment` for the scope of the test. Call
|
|
`BrowserTaskEnvironment::RunUntilIdle()` to wait until all tasks have run.
|
|
|
|
If the test doesn't use `BrowserThread::UI/IO`, instantiate a
|
|
`base::test::TaskEnvironment` for the scope of the test. Call
|
|
`base::test::TaskEnvironment::RunUntilIdle()` to wait until all tasks have
|
|
run.
|
|
|
|
In both cases, you can run tasks until a condition is met. A test that waits for
|
|
a condition to be met is easier to understand and debug than a test that waits
|
|
for all tasks to run.
|
|
|
|
```cpp
|
|
int g_condition = false;
|
|
|
|
base::RunLoop run_loop;
|
|
base::ThreadPool::PostTask(FROM_HERE, {}, base::BindOnce(
|
|
[] (base::OnceClosure closure) {
|
|
g_condition = true;
|
|
std::move(quit_closure).Run();
|
|
}, run_loop.QuitClosure()));
|
|
|
|
// Runs tasks until the quit closure is invoked.
|
|
run_loop.Run();
|
|
|
|
EXPECT_TRUE(g_condition);
|
|
```
|
|
|
|
## Your question hasn't been answered?
|
|
|
|
1. Check the main [Threading and Tasks](threading_and_tasks.md) docs.
|
|
2. Ping
|
|
[scheduler-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/scheduler-dev).
|