https://chromium.googlesource.com/chromium/src/+/main/docs/threading_and_tasks.md
这段代码主要介绍了Chrome浏览器的多进程和多线程架构。
Chrome具有多进程架构,每个进程都是高度多线程的。本文将介绍每个进程共享的基本线程系统。主要目标是保持主线程(在浏览器进程中称为“UI”线程)和IO线程(每个进程用于接收IPC的线程)响应灵敏。这意味着将任何阻塞的I/O或其他昂贵的操作卸载到其他线程。我们的方法是使用消息传递作为线程之间通信的方式。我们不鼓励使用锁定和线程安全的对象。相反,对象只存在于一个(通常是虚拟的——稍后我们将讨论!)线程上,我们通过这些线程之间传递消息进行通信。在没有外部关于延迟或工作负载的要求的情况下,Chrome尝试成为一个高度并发,但不一定是并行,的系统。
关于Chromium进行并发(特别是Sequences)的基本介绍可以在这里找到。
本文档假定对计算机科学线程概念熟悉。
任务:待处理的工作单元。实际上是一个带有可选关联状态的函数指针。在Chrome中,这是通过base::BindOnce和base::BindRepeating创建的base::OnceCallback和base::RepeatingCallback。(文档)。
任务队列:待处理的任务队列。
物理线程:由操作系统提供的线程(例如,POSIX上的pthread或Windows上的CreateThread())。Chrome跨平台抽象是base::PlatformThread。您几乎永远不应该直接使用它。
base::Thread:一个物理线程,永远从专用的任务队列中处理消息,直到Quit()。您几乎永远不应该创建自己的base::Thread。
线程池:拥有共享任务队列的一组物理线程。在Chrome中,这是base::ThreadPoolInstance。每个Chrome进程都有一个实例,它提供通过base/task/thread_pool.h发布的任务,因此您很少需要直接使用base::ThreadPoolInstance API(稍后将详细介绍发布任务)。
顺序或虚拟线程:由Chrome管理的执行线程。就像物理线程一样,在任何给定时刻,只有一个任务可以在给定的顺序/虚拟线程上运行,并且每个任务都会看到前一个任务的副作用。任务是顺序执行的,但在每个任务之间可能会跳过物理线程。
任务运行器:一个接口,通过该接口可以发布任务。在Chrome中,这是base::TaskRunner。
有序任务运行器:保证提交给它的任务按提交顺序依次运行的任务运行器。每个任务都保证能观察到前面任务产生的副作用。提交给有序任务运行器的任务通常由一个线程(虚拟或物理)处理。在Chrome中,这是base::SequencedTaskRunner,即base::TaskRunner的子类。
单线程任务运行器:一种按顺序运行任务的运行器,它确保所有任务都由相同的物理线程处理。在Chrome中,这是base::SingleThreadTaskRunner,它是base::SequencedTaskRunner的子类。只要可能,我们就更喜欢使用序列而不是线程。
给读者的注解:以下术语是为了弥补通用线程术语和我们在Chrome中使用它们的方式之间的鸿沟。如果你刚开始接触,这可能有点难以理解。如果这很难解析,请考虑跳到下面的更详细的部分,并在必要时参考这个。
线程不安全:Chrome中的绝大多数类型都是线程不安全的(设计如此)。访问这些类型/方法必须进行外部同步。线程不安全类型通常要求将所有访问其状态的任务发布到相同的base::SequencedTaskRunner,并在调试构建中通过SEQUENCE_CHECKER成员进行验证。锁也是同步访问的一种选择,但在Chrome中,我们强烈推荐使用序列而非锁。
线程亲和性:这些类型/方法必须始终从相同的物理线程(即从相同的base::SingleThreadTaskRunner)访问,并且通常具有THREAD_CHECKER成员来验证它们是否是。除了使用第三方API或有线程亲和性的叶子依赖项之外,Chrome中几乎没有类型具有线程亲和性的原因。请注意,base::SingleThreadTaskRunner是base::SequencedTaskRunner,因此线程亲和性是线程不安全性的子集。线程亲和性有时也被称为线程恶意。
线程安全:这种类型/方法可以安全地在并行环境中访问。
线程兼容:此类提供对const方法的线程安全并行访问,但对于非const方法(或混合const/非const访问)则需要同步。Chrome不公开读取器-写入器锁;因此,此类型的唯一用例是对象(通常是全局变量),这些对象以线程安全的方式初始化一次(在启动的单线程阶段,或通过线程安全的静态局部初始化模式延迟初始化,例如base::NoDestructor),然后永远保持不变。
不可变:线程兼容类型的子集,在构造后无法修改。
序列友好:这种类型/方法是支持从base::SequencedTaskRunner进行调用的线程不安全类型。理想情况下,所有线程不安全类型都应该支持这种方式,但遗留代码有时会有过度检查,在仅仅是线程不安全的情况下强制执行线程亲和性。有关更多详细信息,请参阅下面的Prefer Sequences to Threads。
每个Chrome进程都有
一个主线程:
大多数线程都有一个从队列中获取任务并运行它们的循环(队列可能在多个线程之间共享)。
Tasks
任务是base::OnceClosure,被添加到队列中以进行异步执行。base::OnceClosure存储函数指针和参数。它具有Run()方法,该方法使用绑定的参数调用函数指针。它使用base::BindOnce创建。(参考Callback<>和Bind()文档)。
void TaskA() {}
void TaskB(int v) {}
auto task_a = base::BindOnce(&TaskA);
auto task_b = base::BindOnce(&TaskB, 42);
一组任务可以以下列方式之一执行:
并行:没有任务执行顺序,可能同时在任何线程上执行。
顺序:按发布顺序执行任务,一次在任何线程上执行一个任务。
单线程:按发布顺序一次执行一个任务,并在单个线程上执行。
强烈推荐串行执行(在虚拟线程上)而不是单线程执行(在物理线程上)。除了绑定到主线程(UI)或IO线程的类型/方法:线程安全性最好通过base::SequencedTaskRunner实现,而不是通过管理自己的物理线程(参考下面“提交一个串行任务”)。
对于“当前物理线程”公开的所有API都有一个等效的“当前序列”(映射)。
如果您编写了一个适合序列化的类型,但在叶子依赖项中未能通过线程亲和性检查(例如THREAD_CHECKER):请考虑使该依赖项也适合序列化。Chrome中的大多数核心API都适合序列化,但是某些遗留类型可能仍然过度热衷于使用ThreadChecker/SingleThreadTaskRunner,而它们本可以依赖“当前序列”而不必再使用线程亲和性。
可以在任何线程上运行,并且与其他任务没有顺序或互斥要求的任务应该使用base/task/thread_pool.h中定义的base::ThreadPool::PostTask*()函数之一发布。
base::ThreadPool::PostTask(FROM_HERE, base::BindOnce(&Task));
这会发布具有默认特征的任务。
base::ThreadPool::PostTask*()函数允许调用者通过TaskTraits提供关于任务的额外细节(参考:使用TaskTraits标注任务)。
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, MayBlock()},
base::BindOnce(&Task));
并行任务运行器(parallel base::TaskRunner)是直接调用base::ThreadPool::PostTask*()的替代方案。这在事先不确定任务是并行、顺序执行,还是在一个线程上执行时特别有用(参考Posting a Sequenced Task和Posting Multiple Tasks to the Same Thread)。由于base::TaskRunner是base::SequencedTaskRunner和base::SingleThreadTaskRunner的基类,一个scoped_refptr成员可以持有base::TaskRunner、base::SequencedTaskRunner或base::SingleThreadTaskRunner。
class A {
public:
A() = default;
void PostSomething() {
task_runner_->PostTask(FROM_HERE, base::BindOnce(&A, &DoSomething));
}
void DoSomething() {
}
private:
scoped_refptr task_runner_ =
base::ThreadPool::CreateTaskRunner({base::TaskPriority::USER_VISIBLE});
};
除非测试需要精确控制任务的执行方式,否则最好直接调用base::ThreadPool::PostTask*()(参考测试中控制任务执行方式的非入侵性测试)。
序列是一组任务,这些任务按发布顺序一次运行(不一定在同一个线程上)。要将任务作为序列的一部分发布,请使用
base::SequencedTaskRunner。
Posting to a New Sequence
base::ThreadPool::CreateSequencedTaskRunner()创建base::SequencedTaskRunner。
scoped_refptr sequenced_task_runner =
base::ThreadPool::CreateSequencedTaskRunner(...);
// TaskB runs after TaskA completes.
sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskA));
sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskB));
Posting to the Current (Virtual) Thread
发布到当前(虚拟)线程的首选方法是通过 base::SequencedTaskRunner::GetCurrentDefault()
// The task will run on the current (virtual) thread’s default task queue.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&Task);
请注意,SequencedTaskRunner::GetCurrentDefault()返回当前虚拟线程的默认队列。在具有多个任务队列(例如BrowserThread::UI)的线程上,这可以是与当前任务所属的队列不同的队列。“当前”任务运行器有意不通过静态获取器公开。要么您已经知道它,并且可以直接向它发布,要么您不知道,并且唯一合理的目标就是默认队列。有关详细讨论,请参见https://bit.ly/3JvCLsX。
Chrome中不建议使用锁。序列本身提供了线程安全性。优先使用始终从同一序列访问的类,而不是使用锁来管理自己的线程安全性。
线程安全但不支持多线程;这是怎么做到的?同一序列的任务将按照顺序运行。当一个序列的任务完成后,下一个任务可能会被不同的工作线程拾取,但该任务会看到由该序列中的前一个任务所造成的任何副作用。
class A {
public:
A() {
// Do not require accesses to be on the creation sequence.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
void AddValue(int v) {
// Check that all accesses are on the same sequence.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
values_.push_back(v);
}
private:
SEQUENCE_CHECKER(sequence_checker_);
// No lock required, because all accesses are on the
// same sequence.
std::vector values_;
};
A a;
scoped_refptr task_runner_for_a = ...;
task_runner_for_a->PostTask(FROM_HERE,
base::BindOnce(&A::AddValue, base::Unretained(&a), 42));
task_runner_for_a->PostTask(FROM_HERE,
base::BindOnce(&A::AddValue, base::Unretained(&a), 27));
// Access from a different sequence causes a DCHECK failure.
scoped_refptr other_task_runner = ...;
other_task_runner->PostTask(FROM_HERE,
base::BindOnce(&A::AddValue, base::Unretained(&a), 1));
锁应该仅用于在多个线程上访问的共享数据结构中进行交换。如果一个线程基于昂贵的计算或通过磁盘访问来更新它,那么应该不持有锁的情况下完成这项耗时的工作。只有在结果可用时才应该使用锁来交换新数据。PluginList::LoadPlugins(content/browser/plugin_list.cc)就是一个例子。如果您必须使用锁,以下是一些最佳做法和要避免的陷阱。
为了编写非阻塞代码,Chrome中的许多API都是异步的。通常这意味着它们要么需要在特定的线程/序列上执行并通过自定义委托接口返回结果,要么它们采用base::OnceCallback(或base::RepeatingCallback<>)对象,该对象在请求的操作完成后被调用。在特定的线程/序列上执行工作在上面的PostTask部分中有所涉及。
如果多个任务需要在同一线程上运行,则将它们发布到base::SingleThreadTaskRunner。发布到同一base::SingleThreadTaskRunner的所有任务都按照发布顺序在同一线程上运行。
Posting to the Main Thread or to the IO Thread in the Browser Process
您可以使用content/public/browser/browser_thread.h中的content::GetUIThreadTaskRunner({})或content::GetIOThreadTaskRunner({})将任务发布到主线程或IO线程。
虽然这些方法可能需要提供额外的BrowserTaskTraits参数,但在BrowserThreads中一般不常用,应该留作高级用例使用。
正在进行迁移(任务API v3),不再使用以前的基础API与特性,您可能仍然可以在整个代码库中找到它(它等效):
base::PostTask(FROM_HERE, {content::BrowserThread::UI}, …);
base::CreateSingleThreadTaskRunner({content::BrowserThread::IO})
->PostTask(FROM_HERE, …);
注意:在迁移期间,很遗憾,您需要继续手动包含content/public/browser/browser_task_traits.h才能使用browser_thread.h API。
主线程和IO线程已经非常繁忙。因此,如果可能,最好发布到一个通用线程(参考发布并行任务,发布顺序任务)。发布到主线程的好理由是更新UI或访问绑定到它的对象(例如Profile)。发布到IO线程的好理由是访问绑定到它的组件的内部(例如IPC,网络)。注意:不需要向IO线程发布一个明确的post任务来发送/接收IPC或在网络上发送/接收数据。
Posting to a Custom SingleThreadTaskRunner
如果多个任务需要在同一个线程上运行,并且该线程不必是主线程或 IO 线程,请将它们发布到由 base::Threadpool::CreateSingleThreadTaskRunner 创建的 base::SingleThreadTaskRunner。
scoped_refptr single_thread_task_runner =
base::Threadpool::CreateSingleThreadTaskRunner(…);
// TaskB runs after TaskA completes. Both tasks run on the same thread.
single_thread_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskA));
single_thread_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskB));
请记住,与物理线程相比,我们更喜欢序列,因此很少需要这样做。
这段文本是在提醒读者在发布任务时需要注意的一些事项。其中涉及到的技术术语包括:
如果无论如何都必须将任务发布到当前物理线程,则应使用base::SingleThreadTaskRunner::CurrentDefaultHandle。
// The task will run on the current thread in the future.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&Task));
Posting Tasks to a COM Single-Thread Apartment (STA) Thread (Windows)
需要在COM单线程 apartment (STA)线程上运行的任务必须被发布到由base::ThreadPool::CreateCOMSTATaskRunner()返回的base::SingleThreadTaskRunner。正如在向同一线程发布多个任务中所提到的,发布到同一base::SingleThreadTaskRunner的所有任务都在同一线程上按照发布顺序运行。
// Task(A|B|C)UsingCOMSTA will run on the same COM STA thread.
void TaskAUsingCOMSTA() {
// [ This runs on a COM STA thread. ]
// Make COM STA calls.
// ...
// Post another task to the current COM STA thread.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&TaskCUsingCOMSTA));
}
void TaskBUsingCOMSTA() { }
void TaskCUsingCOMSTA() { }
auto com_sta_task_runner = base::ThreadPool::CreateCOMSTATaskRunner(...);
com_sta_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskAUsingCOMSTA));
com_sta_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskBUsingCOMSTA));
该任务系统确保在发布任务之前顺序执行的所有内存效果对于任务开始运行时是可见的。更正式地说,对PostTask()的调用和已发布任务的执行是相互之间具有happens-before关系。这对于::base中发布任务的所有变体(包括PostTaskAndReply())都是正确的。同样,对于作为相同SequencedTaskRunner的一部分以序列方式运行的任务,也存在happens-before关系。
了解此保证非常重要,因为Chrome任务通常会访问直接复制到base::OnceCallback中的数据之外的内存,并且此happens-before关系允许避免在任务本身中进行额外的同步。作为一个非常具体的示例,考虑绑定刚刚在发布任务的线程中初始化的内存指针的回调。
还有一个更受限制的模型也值得注意。执行可以被分割成在不同任务运行器上运行的任务。
执行可以分割为在不同任务运行器上运行的任务,其中每个任务专门访问内存中的某些对象,而无需显式同步。发布另一个任务将该“所有权”(对象)转移到下一个任务。通过这种方式,对象所有权的观念通常可以扩展到任务运行器的级别,这提供了有用的不变量来进行推理。此模型允许避免竞争条件,同时避免锁和原子操作。由于其简单性,此模型通常在Chrome中使用。
base::TaskTraits封装了关于任务的信息,这些信息有助于线程池做出更好的调度决策。
当默认特性足够时,使用base::TaskTraits的方法可以在参数中传递{}。默认特性适用于以下任务:
不阻塞(参考MayBlock和WithBaseSyncPrimitives);
与用户阻塞活动有关;(通过与具有阻塞组件的组件的顺序依赖关系,以显式或隐式的方式)
要么阻塞关闭,要么在关闭时跳过(线程池可以自由选择适合的默认选项)。
不符合此描述的任务必须与明确的TaskTraits一起发布。
base/task/task_traits.h提供了可用的特征的详尽文档。内容层还在content/public/browser/browser_task_traits.h中提供了额外的特征,以方便将任务发布到BrowserThread。
下面是一些如何指定base::TaskTraits的示例。
// This task has no explicit TaskTraits. It cannot block. Its priority is
// USER_BLOCKING. It will either block shutdown or be skipped on shutdown.
base::ThreadPool::PostTask(FROM_HERE, base::BindOnce(...));
// This task has the highest priority. The thread pool will schedule it before
// USER_VISIBLE and BEST_EFFORT tasks.
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::USER_BLOCKING},
base::BindOnce(...));
// This task has the lowest priority and is allowed to block (e.g. it
// can read a file from disk).
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::BindOnce(...));
// This task blocks shutdown. The process won't exit before its
// execution is complete.
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(...));
不要在主线程、IO线程或任何预计以低延迟运行任务的序列上执行昂贵的工作。相反,使用base::ThreadPool::PostTaskAndReply*()或base::SequencedTaskRunner::PostTaskAndReply()异步执行昂贵的工作。注意,在IO线程上异步/重叠I/O是可以接受的。
示例:在主线程上运行下面的代码将阻止浏览器在很长一段时间内响应用户输入。
// GetHistoryItemsFromDisk() may block for a long time.
// AddHistoryItemsToOmniboxDropDown() updates the UI and therefore must
// be called on the main thread.
AddHistoryItemsToOmniboxDropdown(GetHistoryItemsFromDisk("keyword"));
下面的代码通过在线程池中安排对GetHistoryItemsFromDisk()的调用,然后对原始序列(在这种情况下是主线程)上的AddHistoryItemsToOmniboxDropdown()进行调用,从而解决了此问题。第一次调用的返回值会自动作为参数提供给第二次调用。
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&GetHistoryItemsFromDisk, "keyword"),
base::BindOnce(&AddHistoryItemsToOmniboxDropdown));
Posting a Task with a Delay
posting a One-Off Task with a Delay
要发布必须在延迟到期后运行一次的任务,请使用 base::ThreadPool::PostDelayedTask*() 或 base::TaskRunner::PostDelayedTask()。
base::ThreadPool::PostDelayedTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT}, base::BindOnce(&Task),
base::Hours(1));
scoped_refptr task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT});
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&Task), base::Hours(1));
注意:具有 1 小时延迟的任务可能不必在延迟到期后立即运行。指定 base::TaskPriority::BEST_EFFORT 以防止其在延迟到期时减慢浏览器速度。
Posting a Repeating Task with a Delay
To post a task that must run at regular intervals, use base::RepeatingTimer.
class A {
public:
~A() {
// The timer is stopped automatically when it is deleted.
}
void StartDoingStuff() {
timer_.Start(FROM_HERE, Seconds(1),
this, &A::DoStuff);
}
void StopDoingStuff() {
timer_.Stop();
}
private:
void DoStuff() {
// This method is called every second on the sequence that invoked
// StartDoingStuff().
}
base::RepeatingTimer timer_;
};
Cancelling a Task
Using base::WeakPtr
base::WeakPtr可以用于确保绑定到某个对象的任何回调在该对象被销毁时被取消。
int Compute() { … }
class A {
public:
void ComputeAndStore() {
// Schedule a call to Compute() in a thread pool followed by
// a call to A::Store() on the current sequence. The call to
// A::Store() is canceled when |weak_ptr_factory_| is destroyed.
// (guarantees that |this| will not be used-after-free).
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&Compute),
base::BindOnce(&A::Store, weak_ptr_factory_.GetWeakPtr()));
}
private:
void Store(int value) { value_ = value; }
int value_;
base::WeakPtrFactory weak_ptr_factory_{this};
};
注意:WeakPtr 不是线程安全的:~WeakPtrFactory() 和 Store()(绑定到 WeakPtr)必须都在同一序列上运行。
Using base::CancelableTaskTracker
base::CancelableTaskTracker 允许以与任务运行顺序不同的顺序进行取消。请记住,CancelableTaskTracker 无法取消已经开始运行的任务。
auto task_runner = base::ThreadPool::CreateTaskRunner({});
base::CancelableTaskTracker cancelable_task_tracker;
cancelable_task_tracker.PostTask(task_runner.get(), FROM_HERE,
base::DoNothing());
// Cancels Task(), only if it hasn't already started running.
cancelable_task_tracker.TryCancelAll();
Posting a Job to run in parallel
base::PostJob是一个高级用户API,用于能够调度单个base::RepeatingCallback工作任务并请求ThreadPool工作人员并行地调用它。这避免了退化情况:
为每个工作项调用PostTask(),导致显著开销。
固定数量的PostTask()调用将工作拆分,并可能运行很长时间。当许多组件发布“num cores”任务并且所有任务都期望使用所有核心时,这是有问题的。在这些情况下,调度器缺乏上下文来公平对待多个相同优先级请求和/或在高优先级工作进入时请求较低优先级工作让路的能力。
请参见base/task/job_perftest.cc以获取完整的示例。
// A canonical implementation of |worker_task|.
void WorkerTask(base::JobDelegate* job_delegate) {
while (!job_delegate->ShouldYield()) {
auto work_item = TakeWorkItem(); // Smallest unit of work.
if (!work_item)
return:
ProcessWork(work_item);
}
}
// Returns the latest thread-safe number of incomplete work items.
void NumIncompleteWorkItems(size_t worker_count) {
// NumIncompleteWorkItems() may use |worker_count| if it needs to account for
// local work lists, which is easier than doing its own accounting, keeping in
// mind that the actual number of items may be racily overestimated and thus
// WorkerTask() may be called when there's no available work.
return GlobalQueueSize() + worker_count;
}
base::PostJob(FROM_HERE, {},
base::BindRepeating(&WorkerTask),
base::BindRepeating(&NumIncompleteWorkItems));
在被调用时在循环中尽可能多地完成工作,工作任务避免了调度开销。同时,base::JobDelegate::ShouldYield()被周期性地调用,以有条件地退出并让调度器优先处理其他工作。这种yield语义允许,例如,一个用户可见的工作使用所有核心,但当一个用户阻塞任务进入时让路。
当添加新工作项并且 API 用户希望有更多线程并行调用辅助任务时,必须在最大并发数增加后不久调用 JobHandle/JobDelegate::NotifyConcurrencyIncrease()
Testing
有关更多详细信息,请参见Testing Components Which Post Tasks。
要测试使用base::SingleThreadTaskRunner::CurrentDefaultHandle、base::SequencedTaskRunner::CurrentDefaultHandle或base/task/thread_pool.h中的函数的代码,请在测试的作用域内实例化一个base::test::TaskEnvironment。如果您需要BrowserThreads,请使用content::BrowserTaskEnvironment而不是base::test::TaskEnvironment。
可以使用base::RunLoop来运行base::test::TaskEnvironment的消息泵,这可以使其运行到Quit()(显式或通过RunLoop::QuitClosure())或者RunUntilIdle()准备好运行的任务并立即返回。TaskEnvironment将RunLoop::Run()配置为在TestTimeouts::action_timeout()之后没有显式退出的情况下GTEST_FAIL()。如果测试的代码未能触发RunLoop退出,则此方法比让测试挂起更好。超时可以使用base::test::ScopedRunLoopTimeout进行覆盖。
class MyTest : public testing::Test {
public:
// ...
protected:
base::test::TaskEnvironment task_environment_;
};
TEST_F(MyTest, FirstTest) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, base::BindOnce(&A));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
base::BindOnce(&B));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindOnce(&C), base::TimeDelta::Max());
// This runs the (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle queue until it is empty.
// Delayed tasks are not added to the queue until they are ripe for execution.
// Prefer explicit exit conditions to RunUntilIdle when possible:
// bit.ly/run-until-idle-with-care2.
base::RunLoop().RunUntilIdle();
// A and B have been executed. C is not ripe for execution yet.
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, base::BindOnce(&D));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, run_loop.QuitClosure());
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, base::BindOnce(&E));
// This runs the (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle queue until QuitClosure is
// invoked.
run_loop.Run();
// D and run_loop.QuitClosure() have been executed. E is still in the queue.
// Tasks posted to thread pool run asynchronously as they are posted.
base::ThreadPool::PostTask(FROM_HERE, {}, base::BindOnce(&F));
auto task_runner =
base::ThreadPool::CreateSequencedTaskRunner({});
task_runner->PostTask(FROM_HERE, base::BindOnce(&G));
// To block until all tasks posted to thread pool are done running:
base::ThreadPoolInstance::Get()->FlushForTesting();
// F and G have been executed.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {}, base::BindOnce(&H), base::BindOnce(&I));
// This runs the (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle queue until both the
// (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle queue and the ThreadPool queue are
// empty. Prefer explicit exit conditions to RunUntilIdle when possible:
// bit.ly/run-until-idle-with-care2.
task_environment_.RunUntilIdle();
// E, H, I have been executed.
}
必须在进程中使用base/task/thread_pool.h中的函数之前初始化ThreadPoolInstance。Chrome浏览器进程和子进程(渲染器,GPU,实用程序)中的ThreadPoolInstance的初始化已经被处理。要在另一个进程中使用ThreadPoolInstance,请在主函数中尽早初始化ThreadPoolInstance:
// This initializes and starts ThreadPoolInstance with default params.
base::ThreadPoolInstance::CreateAndStartWithDefaultParams(“process_name”);
// The base/task/thread_pool.h API can now be used with base::ThreadPool trait.
// Tasks will be scheduled as they are posted.
// This initializes ThreadPoolInstance.
base::ThreadPoolInstance::Create(“process_name”);
// The base/task/thread_pool.h API can now be used with base::ThreadPool trait. No
// threads will be created and no tasks will be scheduled until after Start() is
// called.
base::ThreadPoolInstance::Get()->Start(params);
// ThreadPool can now create threads and schedule tasks.
并在主函数后期关闭 ThreadPoolInstance:
base::ThreadPoolInstance::Get()->Shutdown();
// Tasks posted with TaskShutdownBehavior::BLOCK_SHUTDOWN and
// tasks posted with TaskShutdownBehavior::SKIP_ON_SHUTDOWN that
// have started to run before the Shutdown() call have now completed their
// execution. Tasks posted with
// TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN may still be
// running.
TaskRunner ownership (encourage no dependency injection)
任务运行器不应通过多个组件传递。相反,使用任务运行器的组件应该是创建它的组件。
请看以下重构示例,其中任务运行器通过许多组件传递,只是为了在最终的叶子节点中使用。叶子节点现在可以并应该直接从base/task/thread_pool.h获取其任务运行器。
如上所述,base::test::TaskEnvironment允许单元测试控制从底层任务运行器发布的任务。在测试需要更精确地控制任务排序的罕见情况下:任务运行器的依赖注入可能是有用的。对于这种情况,首选的方法如下:
class Foo {
public:
// Overrides |background_task_runner_| in tests.
void SetBackgroundTaskRunnerForTesting(
scoped_refptr background_task_runner) {
background_task_runner_ = std::move(background_task_runner);
}
private:
scoped_refptr background_task_runner_ =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT});
}
SequenceManager管理具有不同属性(例如优先级、常见任务类型)的TaskQueue,将所有发布的任务复用进入单一后台序列。通常这将是一个MessagePump。根据所使用的消息泵的类型,其他事件(例如UI消息)也可能被处理。在Windows上,也可能处理APC调用(在时间允许的情况下)以及发送到已注册HANDLE集的信号。
MessagePump
MessagePumps负责处理本地消息,并定期向其委托(SequenceManager)提供周期。MessagePumps会注意将委托回调与本地消息处理混合,这样就不会出现一种类型的事件使另一种类型的事件缺乏周期的情况。
有不同的MessagePumpTypes,最常见的是:
DEFAULT:仅支持任务和计时器
UI:支持原生UI事件(例如Windows消息)
IO:支持异步IO(不是文件I/O!)
CUSTOM:用户提供的MessagePump接口的实现
RunLoop
RunLoop 是一个帮助程序类,用于运行与当前线程(通常是 SequenceManager)关联的 RunLoop::Delegate。在堆栈上创建一个 RunLoop 并调用 Run/Quit 来运行嵌套的 RunLoop,但请避免在生产代码中嵌套循环!
Task Reentrancy
SequenceManager具有任务再入保护功能。这意味着,如果正在处理一个任务,则第一个任务完成之前不能启动第二个任务。再入可能在处理任务时发生,并且创建内部消息泵。然后,该内部泵处理本机消息,这可能会隐含地启动内部任务。内部消息泵与对话框(DialogBox)、公共对话框(GetOpenFileName)、OLE函数(DoDragDrop)、打印机函数(StartDoc)以及许多其他函数一起创建。
Sample workaround when inner task processing is needed:
HRESULT hr;
{
CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
hr = DoDragDrop(...); // Implicitly runs a modal message loop.
}
// Process |hr| (the result returned by DoDragDrop()).
CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop 之前,请确保您的任务是可重入(可嵌套)的,并且所有全局变量都是稳定且可访问的。
APIs for general use
用户代码几乎不需要直接访问SequenceManager API,因为这些API是为处理调度任务的代码设计的。相反,您应该使用以下内容:
您可能会在代码或文档中遇到对MessageLoop或MessageLoopCurrent的引用。这些类已不存在,我们正在摆脱对它们的所有引用。base::MessageLoopCurrent已被base::CurrentThread取代,base::MessageLoop的替代品为base::SingleThreadTaskExecutor和base::Test::TaskEnvironment。