[TOC]
Note: Make sure to read the main Threading and Tasks docs first.
A task is posted through the base/task/thread_pool.h
API with TaskTraits
.
-
If
TaskTraits
containBrowserThread::UI
:- The task runs on the main thread.
-
If
TaskTraits
containBrowserThread::IO
:- The task runs on the IO thread.
-
If
TaskTraits
don't containBrowserThread::UI/IO
:-
If the task is posted through a
SingleThreadTaskRunner
obtained fromCreateSingleThreadTaskRunner(..., mode)
:-
Where
mode
isSingleThreadTaskRunnerThreadMode::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
isSingleThreadTaskRunnerThreadMode::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, tasks should generally run on a sequence in a thread pool rather than on a dedicated thread.
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.
But be aware that any task posting back to its "current" sequence can enqueue
itself after that "last" task.
The steps depend on where the task runs (see Where 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).
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 sameDEDICATED 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
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?. 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 rather than in sequence (unless posting many such tasks at which point sequencing can be a useful tool to prevent flooding).
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.
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.:
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.
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.
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.
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.
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);
- Check the main Threading and Tasks docs.
- Ping [email protected].