Visual Studio Code Dev
Visual Studio Code is a free, lightweight and powerful code editor for Windows,
Mac and Linux, based on Electron/Chromium. It has built-in support for
JavaScript, TypeScript and Node.js and a rich extension ecosystem that adds
intellisense, debugging, syntax highlighting etc. for many languages (C++,
Python, Go, Java). It works without too much setup. Get started
here.
It is NOT a full-fledged IDE like Visual Studio. The two are completely
separate products. The only commonality w# Chrome多线程任务处理
线程和任务
Chrome具有多进程架构, 并且每个进程都具有大量多线程。每个进程共享的基本线程系统。主要目标是使主线程(浏览器进程中又称为“ UI”线程)和IO线程(用于处理IPC的每个进程的线程 )保持响应。这意味着将任何阻塞的I / O或其他昂贵的操作分配到其他线程。实现方法是使用消息传递作为线程之间进行通信的方式。不建议使用锁定和线程安全的对象。相反,对象仅存活在一个(通常是虚拟的)线程上,并且在这些线程之间传递消息进行通信。
核心概念
- 任务(Task):要处理的工作单元。有效地具有可选状态的函数指针。在Chrome中,这是
base::Callback
通过base::Bind
(文档)创建的 。 - 任务队列(Task queue):要处理的任务队列。
- 物理线程(Physical thread):操作系统提供的线程(例如POSIX上的pthread或Windows上的CreateThread())。Chrome跨平台抽象为
base::PlatformThread
。应该几乎永远不要直接使用它。 base::Thread
:物理线程永远处理来自专用任务队列的消息,直到Quit()为止。应该几乎永远不要创建自己base::Thread
。- 线程池(Thread pool):具有共享任务队列的物理线程池。在Chrome中,这是
base::ThreadPoolInstance
。每个Chrome流程只有一个实例,它可以处理通过其发布的任务base/task/post_task.h
,因此您几乎不需要base::ThreadPoolInstance
直接使用API(有关稍后发布任务的更多信息)。 - 序列(Sequence)或虚拟线程(Virtual thread):chrome管理的执行线程。像物理线程一样,在任何给定时刻,只有一个任务可以在给定序列/虚拟线程上运行,并且每个任务都可以看到先前任务的副作用。任务按顺序执行,但可能会在每个任务之间跳动物理线程。
- 任务运行器(Task runner):可以通过其发布任务的接口。在Chrome中,它是
base::TaskRunner
。 - 顺序任务运行程序(Sequenced task runner):一个任务运行程序,它保证发布到其上的任务将按发布顺序顺序运行。保证每个这样的任务都会看到其前面任务的副作用。发布到已排序任务运行器的任务通常由单个线程(虚拟或物理)处理。在Chrome中,这是
base::SequencedTaskRunner
-abase::TaskRunner
。 - 单线程任务运行程序(Single-thread task runnet):顺序任务运行程序,可确保所有任务将由同一物理线程处理。在Chrome中,这是
base::SingleThreadTaskRunner
-abase::SequencedTaskRunner
。只要有可能,尽可能使用序列而不是线程。
线程词典
注意:以下术语旨在过渡通用线程命名法与在Chrome中使用它们的方式之间的差距。如果这很难解析,请考虑跳到下面的更详细的部分,并在必要时参考此内容。
- 线程不安全(Thread-unsafe):Chrome中的绝大多数类型都是线程不安全的(根据设计)。对此类类型或方法的访问必须在外部同步。通常,线程不安全类型要求将访问其状态的所有任务都张贴到同一任务,
base::SequencedTaskRunner
并在SEQUENCE_CHECKER
成员的调试版本中对此进行验证。锁也是同步访问的一种方法,但是在Chrome浏览器中,我们强烈推荐序列而不是 锁。 - 线程仿射(Thread-affine):此类类型或方法始终需要从相同的物理线程(即,从
base::SingleThreadTaskRunner
)访问,并且通常具有一个THREAD_CHECKER
成员以验证它们是否正确。缺少使用第三方API或具有叶子依赖关系(线程仿射)的原因:Chrome中几乎没有理由让类型成为线程仿射。注意base::SingleThreadTaskRunner
是一个,base::SequencedTaskRunner
所以线程仿射是线程不安全的子集。线程仿射有时也称为线程恶意。 - 线程安全的(Thread-safe):可以安全地同时访问此类类型或方法。
- 线程兼容(Thread-compatible):此类提供对const方法的安全并发访问,但需要对非const(或混合const /非const访问)进行同步。Chrome不会公开读写器锁;这样,唯一的用例就是对象(通常是全局对象),这些对象以线程安全的方式(在启动的单线程阶段或通过线程安全的静态局部局部化范式惰性地初始化)
base::NoDestructor
),并且永远不变。 - 不可变(Immutable):线程兼容类型的子集,在构造后无法修改。
- 顺序友好的(Sequence-friendly):此类类型或方法是线程不安全的类型,支持从中调用
base::SequencedTaskRunner
。理想情况下,所有线程不安全类型都是这种情况,但是旧版代码有时会进行过分的检查,仅在线程不安全的情况下就强制执行线程亲和力。有关更多详细信息,请参见下面的“优先选择线程”。
线程数
每个Chrome进程都有
主线程
- 在浏览器进程中(BrowserThread :: UI):更新UI
- 在渲染器进程(Blink主线程)中:运行大多数Blink
IO线程
- 在浏览器进程中(BrowserThread :: IO):处理IPC和网络请求
- 在渲染器进程中:处理IPC
- 一些专用线程
- 和通用线程池
大多数线程都有一个循环,该循环从队列中获取任务并运行它们(该队列可以在多个线程之间共享)。
任务
任务被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);
可以通过以下方式之一执行一组任务:
- 并行(Parallel):无任务执行顺序,可能在任何线程上一次全部执行
- 已排序(Sequenced):以发布顺序执行的任务,在任何线程上一次执行。
- 单线程(Single Threaded):以发布顺序执行的任务,一次在一个线程上执行。
- COM Single Threaded:COM初始化的单线程的变体。
优先选择序列而不是物理线程
顺序执行(在虚拟线程上)比单线程执行(在物理线程上)更受青睐。除了绑定到主线程(UI)或IO线程的类型或方法外,base::SequencedTaskRunner
通过管理自己的物理线程比通过管理自己的物理线程更好地实现了线程安全 (请参见下面的发布序列化任务)。
对于“当前物理线程”公开的所有API都具有“当前序列”(映射)的等效项。
如果您发现自己写的是序列友好类型,并且THREAD_CHECKER
在叶子依赖项中未通过线程亲和力检查(例如),请考虑使该依赖项也对序列友好。Chrome中的大多数核心API都是顺序友好的,但是某些传统类型可能仍然过分地使用ThreadChecker / ThreadTaskRunnerHandle / SingleThreadTaskRunner来代替它们依赖“当前序列”,而不再是仿射。
发布并行任务
直接发布到线程池
可以在任何线程上运行并且与其他任务没有排序或互斥要求的任务应使用中base::PostTask*()
定义的功能 之一发布 base/task/post_task.h
。
base::PostTask(FROM_HERE, base::BindOnce(&Task));
这将发布具有默认特征的任务。
该base::PostTask*()
函数允许调用者通过TaskTraits提供有关任务的其他详细信息(请参阅使用TaskTraits注释任务)。
base::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, MayBlock()},
base::BindOnce(&Task));
通过TaskRunner发布
并行 base::TaskRunner
是base::PostTask*()
直接调用的替代方法。当事先不知道任务是并行,按顺序还是单线程发布时,这尤其有用(请参阅发布序列化任务,将多个任务发布到同一线程)。因为base::TaskRunner
是base::SequencedTaskRunner
and 的基类base::SingleThreadTaskRunner
,所以scoped_refptr<TaskRunner>
成员可以容纳a base::TaskRunner
,abase::SequencedTaskRunner
或a base::SingleThreadTaskRunner
。
class A {
public:
A() = default;
void DoSomething() {
task_runner_->PostTask(FROM_HERE, base::BindOnce(&A));
}
private:
scoped_refptr<base::TaskRunner> task_runner_ =
base::CreateTaskRunner({base::TaskPriority::USER_VISIBLE});
};
除非测试需要精确控制任务的执行方式,否则最好base::PostTask*()
直接调用(请参阅测试,以控制测试中的侵入性较小的方法)。
发布顺序任务
序列是一组任务,这些任务以发布顺序一次运行(不一定在同一线程上)。要将任务发布为序列的一部分,请使用 base::SequencedTaskRunner
。
发布到新序列(Posting to a New Sequence)
一个base::SequencedTaskRunner
可以通过以下方式创建 base::CreateSequencedTaskRunner()
。
scoped_refptr<SequencedTaskRunner> sequenced_task_runner =
base::CreateSequencedTaskRunner(...);
// TaskB runs after TaskA completes.
sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskA));
sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskB));
发布到当前(虚拟)线程
向当前线程发布的首选方式是通过 base::CurrentThread
trait。
// The task will run on the current (virtual) thread's default task queue.
base::PostTask(FROM_HERE, {base::CurrentThread()}, base::BindOnce(&Task));
您可以选择指定其他特征。这很重要,因为某些线程(例如浏览器UI线程,浏览器IO线程和Blink主线程)将多个任务队列集中到同一线程中,并且默认优先级可能不适合您的任务。
例如,您可以显式设置优先级:
// The task will run on the current (virtual) thread's best effort queue.
// NOTE only the Browser UI and Browser IO threads support task priority (for
// now), other (virtual) threads will silently ignore traits used in combination
// with `base::CurrentThread`.
base::PostTask(FROM_HERE,
{base::CurrentThread(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&Task));
该base::SequencedTaskRunner
可通过以下方式获得其当前任务被派驻到 base::GetContinuationTaskRunner()
。
在某些线程上,只有一个任务运行程序,因此当前序列与当前线程相同。在浏览器UI,浏览器IO或Blink主线程中不是这种情况。此外,并行base::GetContinuationTaskRunner()
线程池任务或没有任务运行时,当前序列的概念不存在, 在这种情况下将进行DCHECK处理。
注意:虽然base::GetContinuationTaskRunner()
从并行任务调用无效,但对有序任务或单线程任务有效。即来自base::SequencedTaskRunner
或base::SingleThreadTaskRunner
。
// The task will run after any task that has already been posted
// to the SequencedTaskRunner to which the current task was posted
// (in particular, it will run after the current task completes).
// It is also guaranteed that it won’t run concurrently with any
// task posted to that SequencedTaskRunner.
base::GetContinuationTaskRunner()->
PostTask(FROM_HERE, base::BindOnce(&Task));
您也可以通过base::CurrentThread
特征获取当前线程的默认任务运行程序 ,但是您可以指定其他特征。这很重要,因为某些线程(例如浏览器UI线程和Blink主线程)将多个任务队列集中到同一线程中,并且默认优先级可能不适合您的任务。例如,您可以显式设置优先级:
// The task will run on the current (virtual) thread's best effort queue.
// NOTE only the Browser UI and Browser IO threads support task priority, other
// (virtual) threads will silently ignore traits used in combination with
// `base::CurrentThread`.
base::PostTask(FROM_HERE,
{base::CurrentThread(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&Task));
如果您需要获得具有这些特征的任务执行者,则可以通过 base::CreateSequencedTaskRunner()
。
// Tasks posted to |task_runner| will run on the current (virtual) thread's best
// effort queue.
auto task_runner = base::CreateSequencedTaskRunner(
{base::CurrentThread(), base::TaskPriority::BEST_EFFORT});
使用序列替代锁
在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<int> values_;
};
A a;
scoped_refptr<SequencedTaskRunner> 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<SequencedTaskRunner> 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::Callback<>
在请求的操作完成时调用的对象。上面的PostTask部分中介绍了在特定线程/序列上执行工作。
将多个任务发布到同一个线程
如果多个任务需要在同一线程上运行,请将其发布到 base::SingleThreadTaskRunner
。所有发布到同一任务的任务都在base::SingleThreadTaskRunner
按照发布顺序在同一线程上运行。
在浏览器进程中发布到主线程或IO线程
base::PostTask(FROM_HERE, {content::BrowserThread::UI}, ...);
base::CreateSingleThreadTaskRunner({content::BrowserThread::IO})
->PostTask(FROM_HERE, ...);
主线程和IO线程已经非常忙。因此,在可能的情况下,最好将其发布到通用线程中(请参阅 发布并行任务, 发布序列化任务)。发布到主线程的充分理由是更新UI或访问与其绑定的对象(例如Profile
)。发布到IO线程的一个好理由是访问与其绑定的组件的内部(例如IPC,网络)。注意:不需要对IO线程进行明确的发布任务即可在网络上发送/接收IPC或发送/接收数据。
在渲染器过程中发布到主线程
发布到自定义SingleThreadTaskRunner
如果多个任务需要在同一个线程上运行,而该线程不必是主线程或IO线程,则将其发布到base::SingleThreadTaskRunner
创建者base::CreateSingleThreadTaskRunner
。
scoped_refptr<SingleThreadTaskRunner> single_thread_task_runner =
base::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));
将任务发布到COM单线程单元(STA)线程(Windows)
任务是需要在一个COM单线程公寓(STA)线程上运行必须张贴到一个base::SingleThreadTaskRunner
由归国 base::CreateCOMSTATaskRunner()
。如将多个任务发布到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::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TaskCUsingCOMSTA));
}
void TaskBUsingCOMSTA() { }
void TaskCUsingCOMSTA() { }
auto com_sta_task_runner = base::CreateCOMSTATaskRunner(...);
com_sta_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskAUsingCOMSTA));
com_sta_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskBUsingCOMSTA));
使用TaskTraits注释任务
base::TaskTraits
封装有关有助于线程池做出更好的调度决策的任务的信息。
中的所有base::PostTask*()
函数 base/task/post_task.h
都有一个以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 inherited from the calling context (e.g. if it is posted from
// a BEST_EFFORT task, it will have a BEST_EFFORT priority). It will either
// block shutdown or be skipped on shutdown.
base::PostTask(FROM_HERE, base::BindOnce(...));
// This task has the highest priority. The thread pool will try to
// run it before USER_VISIBLE and BEST_EFFORT tasks.
base::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::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::PostTask(
FROM_HERE, {base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(...));
// This task will run on the Browser UI thread.
base::PostTask(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(...));
// This task will run on the current virtual thread (sequence).
base::PostTask(
FROM_HERE, {base::CurrentThread()},
base::BindOnce(...));
// This task will run on the current virtual thread (sequence) with best effort
// priority.
base::PostTask(
FROM_HERE, {base::CurrentThread(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(...));
保持浏览器响应
不要在主线程,IO线程或任何期望以低延迟运行任务的序列上执行昂贵的工作。而是使用base::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::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&GetHistoryItemsFromDisk, "keyword"),
base::BindOnce(&AddHistoryItemsToOmniboxDropdown));
延迟发布任务
延迟发布一次性任务
要发布必须在延迟到期后运行一次的任务,请使用 base::PostDelayedTask*()
或base::TaskRunner::PostDelayedTask()
。
base::PostDelayedTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT}, base::BindOnce(&Task),
base::TimeDelta::FromHours(1));
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::CreateSequencedTaskRunner({base::TaskPriority::BEST_EFFORT});
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&Task), base::TimeDelta::FromHours(1));
注意 :延迟1小时的任务在其延迟到期时可能不必立即运行。指定base::TaskPriority::BEST_EFFORT
以防止其延迟到期后降低浏览器的速度。
延迟发布重复任务
要发布必须定期运行的任务,请使用base::RepeatingTimer
。
class A {
public:
~A() {
// The timer is stopped automatically when it is deleted.
}
void StartDoingStuff() {
timer_.Start(FROM_HERE, TimeDelta::FromSeconds(1),
this, &MyClass::DoStuff);
}
void StopDoingStuff() {
timer_.Stop();
}
private:
void DoStuff() {
// This method is called every second on the sequence that invoked
// StartDoingStuff().
}
base::RepeatingTimer timer_;
};
取消任务
使用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::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<A> weak_ptr_factory_{this};
};
注意:WeakPtr
不是线程安全的:GetWeakPtr()
,~WeakPtrFactory()
和和 Compute()
(绑定到WeakPtr
)必须全部按相同的顺序运行。
使用base :: CancelableTaskTracker
base::CancelableTaskTracker
允许取消以与执行任务的顺序不同的顺序进行。请记住,CancelableTaskTracker
无法取消已经开始运行的任务。
auto task_runner = base::CreateTaskRunner({base::ThreadPool()});
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();
测试
有关更多详细信息,请参见测试发布任务的组件。
要测试的代码的用途base::ThreadTaskRunnerHandle
, base::SequencedTaskRunnerHandle
或在一个函数 base/task/post_task.h
,实例化一个 base::test::TaskEnvironment
用于测试的范围。如果您需要BrowserThreads,请使用 content::BrowserTaskEnvironment
代替 base::test::TaskEnvironment
。
测试可以base::test::TaskEnvironment
使用来运行的消息泵 base::RunLoop
,可以使它运行到Quit()
(明确地或通过 RunLoop::QuitClosure()
),或者RunUntilIdle()
运行到准备运行的任务并立即返回。
如果尚未在TestTimeouts :: action_timeout()之后明确退出,则TaskEnvironment将RunLoop :: Run()配置为LOG(FATAL)。如果被测代码未能触发RunLoop退出,则最好挂起测试。可以使用ScopedRunTimeoutForTest覆盖超时。
class MyTest : public testing::Test {
public:
// ...
protected:
base::test::TaskEnvironment task_environment_;
};
TEST(MyTest, MyTest) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&A));
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
base::BindOnce(&B));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::BindOnce(&C), base::TimeDelta::Max());
// This runs the (Thread|Sequenced)TaskRunnerHandle queue until it is empty.
// Delayed tasks are not added to the queue until they are ripe for execution.
base::RunLoop().RunUntilIdle();
// A and B have been executed. C is not ripe for execution yet.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&D));
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop.QuitClosure());
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&E));
// This runs the (Thread|Sequenced)TaskRunnerHandle 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::PostTask(FROM_HERE, {base::ThreadPool()}, base::BindOnce(&F));
auto task_runner =
base::CreateSequencedTaskRunner({base::ThreadPool()});
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::PostTaskAndReplyWithResult(
FROM_HERE, base::TaskTrait(),
base::BindOnce(&H), base::BindOnce(&I));
// This runs the (Thread|Sequenced)TaskRunnerHandle queue until both the
// (Thread|Sequenced)TaskRunnerHandle queue and the TaskSchedule queue are
// empty:
task_environment_.RunUntilIdle();
// E, H, I have been executed.
}
在新进程中使用ThreadPool
需要先在进程中初始化ThreadPoolInstance,然后base/task/post_task.h
才能使用其中的功能 。已经处理了Chrome浏览器进程和子进程(渲染器,GPU,实用程序)中ThreadPoolInstance的初始化。要在另一个进程中使用ThreadPoolInstance,请在main函数的早期初始化ThreadPoolInstance:
// This initializes and starts ThreadPoolInstance with default params.
base::ThreadPoolInstance::CreateAndStartWithDefaultParams(“process_name”);
// The base/task/post_task.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/post_task.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所有权(鼓励不进行依赖注入)
TaskRunners不应通过多个组件传递。相反,使用TaskRunner的组件应该是创建它的组件。
请参阅此重构示例,其中TaskRunner通过许多组件传递,仅在最终的叶子中使用。叶子现在可以并且应该直接从那里获取其TaskRunner base/task/post_task.h
。
如上所述,base::test::TaskEnvironment
允许单元测试控制从基础TaskRunner发布的任务。在少数情况下,测试需要更精确地控制任务的顺序:TaskRunners的依赖项注入会很有用。对于这种情况,首选方法如下:
class Foo {
public:
// Overrides |background_task_runner_| in tests.
void SetBackgroundTaskRunnerForTesting(
scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
background_task_runner_ = std::move(background_task_runner);
}
private:
scoped_refptr<base::SequencedTaskRunner> background_task_runner_ =
base::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT});
}
请注意,由于单元测试将直接使用叶层,因此这仍然允许删除// chrome和该组件之间的所有管道层。
旧API
该代码库包含几个旧的API,用于检索当前线程和当前序列的任务运行程序。这些正在迁移到新的API,不应在新的代码中使用。
base::ThreadTaskRunnerHandle
返回当前线程的默认任务运行程序。所有呼叫站点都将迁移为使用base :: CurrentThread。
// The task will run on the current thread in the future.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&Task));
base::SequencedTaskRunnerHandle::Get()
返回线程的默认任务运行器(浏览器UI线程,浏览器IO线程,Blink邮件线程),或者在已排序的线程池任务中返回当前序列。所有呼叫站点都将迁移为使用base :: CurrentThread或base::GetContinuationTaskRunner()
取决于呼叫站点
// The task will run after any task that has already been posted
// to the SequencedTaskRunner to which the current task was posted
// (in particular, it will run after the current task completes).
// It is also guaranteed that it won’t run concurrently with any
// task posted to that SequencedTaskRunner.
base::SequencedTaskRunnerHandle::Get()->
PostTask(FROM_HERE, base::BindOnce(&Task));
回调<>和Bind()
模板化的base::Callback<>
类是通用函数对象。它们与base::Bind()
base / bind.h中的函数一起,提供了一种用于执行部分函数应用程序的类型安全方法。
部分应用程序(或“ currying”)是将函数参数的子集绑定以产生另一个需要较少参数的函数的过程。这可以用来传递延迟执行的单位,就像其他语言中使用的词法闭包一样。例如,它在Chromium代码中用于调度不同MessageLoops上的任务。
没有未绑定输入参数(base::Callback<void()>
)的回调称为base::Closure
。请注意,这与其他语言称为闭包的方式不同-它不保留对其封闭环境的引用。
OnceCallback <>和RepeatingCallback <>
base::OnceCallback<>
并且base::RepeatingCallback<>
是下一代回调类,它正在开发中。
base::OnceCallback<>
由创建base::BindOnce()
。这是仅可移动类型的回调变体,只能运行一次。默认情况下,这会将绑定参数从其内部存储移至绑定函数,因此,可移动类型更易于使用。这应该是首选的回调类型:由于回调的寿命很明确,因此更容易推断何时破坏了线程之间传递的回调。
base::RepeatingCallback<>
由创建base::BindRepeating()
。这是一个可复制的回调变体,可以多次运行。它使用内部引用计数使副本便宜。但是,由于所有权是共享的,因此很难推断何时破坏回调和绑定状态,尤其是在线程之间传递回调时。
该旧版base::Callback<>
当前别名为 base::RepeatingCallback<>
。在新代码中,请base::OnceCallback<>
尽可能使用,base::RepeatingCallback<>
否则使用。迁移完成后,将删除类型别名,base::OnceCallback<>
并将其重命名base::Callback<>
以强调它是首选。
base::RepeatingCallback<>
可base::OnceCallback<>
通过隐式转换转换为。
内存管理和传递
base::{Once,Repeating}Callback
如果所有权转移,则按值传递对象;否则,通过const-reference传递它。
// |Foo| just refers to |cb| but doesn't store it nor consume it.
bool Foo(const base::OnceCallback<void(int)>& cb) {
return cb.is_null();
}
// |Bar| takes the ownership of |cb| and stores |cb| into |g_cb|.
base::RepeatingCallback<void(int)> g_cb;
void Bar(base::RepeatingCallback<void(int)> cb) {
g_cb = std::move(cb);
}
// |Baz| takes the ownership of |cb| and consumes |cb| by Run().
void Baz(base::OnceCallback<void(int)> cb) {
std::move(cb).Run(42);
}
// |Qux| takes the ownership of |cb| and transfers ownership to PostTask(),
// which also takes the ownership of |cb|.
void Qux(base::RepeatingCallback<void(int)> cb) {
PostTask(FROM_HERE, base::BindOnce(cb, 42));
PostTask(FROM_HERE, base::BindOnce(std::move(cb), 43));
}
将base::{Once,Repeating}Callback
对象传递给函数参数时,std::move()
如果不需要保留对其的引用,请使用,否则,直接传递对象。当函数需要排他所有权,并且没有通过移动传递回调时,您可能会看到编译错误。请注意,move-frombase::{Once,Repeating}Callback
变为null,就好像它的Reset()
方法已被调用一样。之后,其is_null()
方法将返回true,而其operator bool()
将返回false。
基本内容快速参考
绑定裸函数
int Return5() { return 5; }
base::OnceCallback<int()> func_cb = base::BindOnce(&Return5);
LOG(INFO) << std::move(func_cb).Run(); // Prints 5.
int Return5() { return 5; }
base::RepeatingCallback<int()> func_cb = base::BindRepeating(&Return5);
LOG(INFO) << func_cb.Run(); // Prints 5.
绑定无捕获的Lambda
base::Callback<int()> lambda_cb = base::Bind([] { return 4; });
LOG(INFO) << lambda_cb.Run(); // Print 4.
base::OnceCallback<int()> lambda_cb2 = base::BindOnce([] { return 3; });
LOG(INFO) << std::move(lambda_cb2).Run(); // Print 3.
绑定捕获的Lambda(测试中)
#include "base/test/bind_test_util.h"
int i = 2;
base::Callback<void()> lambda_cb = base::BindLambdaForTesting([&]() { i++; });
lambda_cb.Run();
LOG(INFO) << i; // Print 3;
绑定类方法
要绑定的第一个参数是要调用的成员函数,第二个是要对其调用的对象。
class Ref : public base::RefCountedThreadSafe<Ref> {
public:
int Foo() { return 3; }
};
scoped_refptr<Ref> ref = new Ref();
base::Callback<void()> ref_cb = base::Bind(&Ref::Foo, ref);
LOG(INFO) << ref_cb.Run(); // Prints out 3.
默认情况下,该对象必须支持RefCounted,否则会出现编译器错误。如果要在线程之间传递,请确保它是RefCountedThreadSafe!如果您不想使用引用计数,请参见下面的“成员函数的高级绑定”。
运行回调
回调可以使用其Run
方法运行,该方法的签名与回调的模板参数相同。请注意,它base::OnceCallback::Run
消耗了回调对象,并且只能在回调右值上调用。
void DoSomething(const base::Callback<void(int, std::string)>& callback) {
callback.Run(5, "hello");
}
void DoSomethingOther(base::OnceCallback<void(int, std::string)> callback) {
std::move(callback).Run(5, "hello");
}
RepeatingCallbacks可以运行多次(运行时不会被删除或标记)。但是,这排除了使用base::Passed
(请参见下文)。
void DoSomething(const base::RepeatingCallback<double(double)>& callback) {
double myresult = callback.Run(3.14159);
myresult += callback.Run(2.71828);
}
如果运行回调可能导致其自身销毁(例如,如果回调接收者删除了该回调所属的对象),则应先移动回调,然后才能安全地调用它。(请注意,这仅是RepeatingCallbacks的问题,因为必须一直移动一次OneCallback才能执行。)
void Foo::RunCallback() {
std::move(&foo_deleter_callback_).Run();
}
创建没有任何作用的回调
有时,您需要一个在运行时不执行任何操作的回调(例如,不希望通知某些事件类型的测试代码)。可能很想传递正确类型的默认构造的回调:
using MyCallback = base::OnceCallback<void(bool arg)>;
void MyFunction(MyCallback callback) {
std::move(callback).Run(true); // Uh oh...
}
...
MyFunction(MyCallback()); // ...this will crash when Run()!
默认构造的回调为null,因此不能为Run()。而是使用 base::DoNothing()
:
...
MyFunction(base::DoNothing()); // Can be Run(), will no-op
base::DoNothing()
可以为任何返回void的OnceCallback或RepeatingCallback传递。
在实现方面,base::DoNothing()
实际上是一个函子,它从中产生一个回调operator()
。这使得在尝试将其他参数绑定到它时不可用。通常,将参数绑定到DoNothing()的唯一原因是为了管理对象的生存期,在这种情况下,您应该努力使用DeleteSoon(),ReleaseSoon()或RefCountedDeleteOnSequence之类的习惯用法。如果确实需要将参数绑定到DoNothing(),或者需要显式创建回调对象(因为通过operator()()进行的隐式转换将不会编译),则可以直接实例化:
// Binds |foo_ptr| to a no-op OnceCallback takes a scoped_refptr<Foo>.
// ANTIPATTERN WARNING: This should likely be changed to ReleaseSoon()!
base::Bind(base::DoNothing::Once<scoped_refptr<Foo>>(), foo_ptr);
传递未绑定的输入参数
未绑定参数是在回调为时指定的Run()
。它们在base::Callback
模板类型中指定:
void MyFunc(int i, const std::string& str) {}
base::Callback<void(int, const std::string&)> cb = base::Bind(&MyFunc);
cb.Run(23, "hello, world");
传递绑定输入参数
在创建回调时,将绑定参数指定为的参数 base::Bind()
。它们将被传递给函数,并且Run()
回调函数的ner看不到那些值,甚至不知道它正在调用的函数。
void MyFunc(int i, const std::string& str) {}
base::Callback<void()> cb = base::Bind(&MyFunc, 23, "hello world");
cb.Run();
没有未绑定输入参数(base::Callback<void()>
)的回调称为base::Closure
。所以我们也可以这样写:
base::Closure cb = base::Bind(&MyFunc, 23, "hello world");
调用成员函数时,绑定参数仅在对象指针之后。
base::Closure cb = base::Bind(&MyClass::MyFunc, this, 23, "hello world");
参数的部分绑定(固化)
您可以在创建回调时指定一些参数,并在执行回调时指定其余参数。
调用函数时,绑定参数是第一个,然后是未绑定参数。
void ReadIntFromFile(const std::string& filename,
base::OnceCallback<void(int)> on_read);
void DisplayIntWithPrefix(const std::string& prefix, int result) {
LOG(INFO) << prefix << result;
}
void AnotherFunc(const std::string& file) {
ReadIntFromFile(file, base::BindOnce(&DisplayIntWithPrefix, "MyPrefix: "));
};
这项技术被称为Curinging。应该使用它代替创建包含绑定参数的适配器类。还请注意,"MyPrefix: "
参数实际上是a const char*
,而DisplayIntWithPrefix
实际上需要a const std::string&
。像正常的函数分派一样base::Bind
,如果可能,将强制参数类型。
避免使用回调参数进行复制
base::BindRepeating()
或的参数base::BindOnce()
作为右值传递时,将被移入其内部存储。
std::vector<int> v = {1, 2, 3};
// |v| is moved into the internal storage without copy.
base::Bind(&Foo, std::move(v));
// The vector is moved into the internal storage without copy.
base::Bind(&Foo, std::vector<int>({1, 2, 3}));
base::BindOnce()
如有可能,与绑定的参数始终移至目标函数。按值传递并具有移动构造函数的函数参数将被移动而不是复制。这样可以轻松地将仅移动类型与一起使用base::BindOnce()
。
相反,与参数绑定的参数base::BindRepeating()
只有在与参数绑定的情况下才会移动到目标函数base::Passed()
。
危险:base::RepeatingCallback
如果将参数与绑定,则A只能运行一次 base::Passed()
。因此,请避免base::Passed()
。如果您知道回调仅会被调用一次,则最好重构代码以供使用base::OnceCallback
。
避免base::Passed()
与和一起使用base::BindOnce()
,std::move()
并且更熟悉。
void Foo(std::unique_ptr<int>) {}
auto p = std::make_unique<int>(42);
// |p| is moved into the internal storage of Bind(), and moved out to |Foo|.
base::BindOnce(&Foo, std::move(p));
base::BindRepeating(&Foo, base::Passed(&p)); // Ok, but subtle.
base::BindRepeating(&Foo, base::Passed(std::move(p))); // Ok, but subtle.
快速参考进行高级绑定
用弱指针绑定类方法
如果MyClass
有一个base::WeakPtr<MyClass> weak_this_
成员(请参阅下文),则可以使用以下方法绑定类方法:
base::Bind(&MyClass::Foo, weak_this_);
如果对象已被销毁,则回调将不会运行。
请注意,绑定到base::WeakPtr
s的类方法回调只能在销毁对象的相同序列上运行,因为否则回调的执行可能会与对象的删除竞争。
base::WeakPtr
与配合使用base::Bind()
,MyClass
通常如下所示:
class MyClass {
public:
MyClass() {
weak_this_ = weak_factory_.GetWeakPtr();
}
private:
base::WeakPtr<MyClass> weak_this_;
// MyClass member variables go here.
base::WeakPtrFactory<MyClass> weak_factory_{this};
};
weak_factory_
是其中的最后一个成员变量,MyClass
因此将其首先销毁。这样可以确保,如果绑定的任何类方法weak_this_
都Run()
在拆卸期间,则将不会实际执行它们。
如果MyClass
仅base::Bind()
按相同的顺序执行s并执行回调,则通常可以安全地调用weak_factory_.GetWeakPtr()
该 base::Bind()
调用,而不是weak_this_
在构造过程中单独进行调用。
将类方法与手动生命周期管理绑定
base::Bind(&MyClass::Foo, base::Unretained(this));
这将禁用该对象上的所有生存期管理。您有责任确保在调用时该对象仍处于活动状态。您打破它,拥有它!
绑定类方法并让类拥有回调
MyClass* myclass = new MyClass;
base::Bind(&MyClass::Foo, base::Owned(myclass));
回调销毁后,该对象将被删除,即使它没有运行(就像您在关机期间发布任务一样)。对于“解雇”案件可能很有用。
std::unique_ptr<>
还支持将智能指针(例如)用作接收器。
std::unique_ptr<MyClass> myclass(new MyClass);
base::Bind(&MyClass::Foo, std::move(myclass));
忽略返回值
有时您想调用一个函数,该函数在不期望返回值的回调中返回值。
int DoSomething(int arg) { cout << arg << endl; }
base::Callback<void(int)> cb =
base::Bind(IgnoreResult(&DoSomething));
将参数绑定到Bind()的快速参考
绑定参数被指定为该函数的参数base::Bind()
并传递给该函数。没有参数或没有未绑定参数的回调称为base::Closure
(base::Callback<void()>
和base::Closure
是同一件事)。
传递回调拥有的参数
void Foo(int* arg) { cout << *arg << endl; }
int* pn = new int(1);
base::Closure foo_callback = base::Bind(&foo, base::Owned(pn));
即使未运行回调,该参数也会在销毁后删除(例如,您在关机期间发布任务)。
将参数作为unique_ptr传递
void TakesOwnership(std::unique_ptr<Foo> arg) {}
auto f = std::make_unique<Foo>();
// f becomes null during the following call.
base::OnceClosure cb = base::BindOnce(&TakesOwnership, std::move(f));
该参数的所有权将一直存在于回调中,直到运行该回调,然后所有权才传递给回调函数。这意味着回调只能运行一次。如果回调从不运行,则销毁对象时将删除该对象。
将参数作为scoped_refptr传递
void TakesOneRef(scoped_refptr<Foo> arg) {}
scoped_refptr<Foo> f(new Foo);
base::Closure cb = base::Bind(&TakesOneRef, f);
这应该“有效”。只要它还处于活动状态,该闭包将采用一个引用,并且对调用的函数将采用另一个引用。
void DontTakeRef(Foo* arg) {}
scoped_refptr<Foo> f(new Foo);
base::Closure cb = base::Bind(&DontTakeRef, base::RetainedRef(f));
base::RetainedRef
保留对对象的引用,并在运行回调时将原始指针传递给该对象。
通过引用传递参数
除非使用或,否则将复制参考。例:std::ref
`std::cref`
void foo(const int& arg) { printf("%d %p\n", arg, &arg); }
int n = 1;
base::Closure has_copy = base::Bind(&foo, n);
base::Closure has_ref = base::Bind(&foo, std::cref(n));
n = 2;
foo(n); // Prints "2 0xaaaaaaaaaaaa"
has_copy.Run(); // Prints "1 0xbbbbbbbbbbbb"
has_ref.Run(); // Prints "2 0xaaaaaaaaaaaa"
通常,参数会复制到闭包中。 危险:std::ref
并std::cref
存储引用原始参数的(常量)引用。这意味着您必须确保对象的寿命超过了回调!
实施说明
该设计来自何处:
的设计base::Callback
和base::Bind
沉重的C ++影响的 tr1::function
/ tr1::bind
,并通过谷歌内部使用的‘谷歌回拨’制度。
自定义行为
有几个注入点可以从其实现的外部控制绑定行为。
namespace base {
template <typename Receiver>
struct IsWeakReceiver {
static constexpr bool value = false;
};
template <typename Obj>
struct UnwrapTraits {
template <typename T>
T&& Unwrap(T&& obj) {
return std::forward<T>(obj);
}
};
} // namespace base
如果base::IsWeakReceiver<Receiver>::value
在方法base::Bind
的接收器上为true ,则 检查接收器的评估结果是否为true,并在其评估为false时取消调用。您可以专门base::IsWeakReceiver
将外部智能指针作为弱指针。
base::UnwrapTraits<BoundObject>::Unwrap()
在base::Callback
调用目标函数之前,为每个绑定的参数调用。您可以专注这个定义参数的包装,例如base::Unretained
,base::Owned
, base::RetainedRef
和base::Passed
。
实施工作原理:
系统包含三个主要组件:
- 该
base::Callback<>
班。 - 该
base::Bind()
功能。 - 参数包装器(例如
base::Unretained()
和base::Owned()
)。
回调类表示通用函数指针。在内部,它存储了代表目标函数及其所有绑定参数的状态折算的状态。该base::Callback
构造需要 base::BindStateBase*
,这是从upcasted base::BindState<>
。在构造函数的上下文中,此base::BindState<>
指针的静态类型唯一标识其表示的功能,其所有绑定参数以及Run()
能够调用目标的方法。
base::Bind()
创建base::BindState<>
具有完全静态类型的,并擦除目标函数类型以及绑定参数的类型。它通过存储指向特定Run()
函数的指针,并将其状态上载到base::BindState<>*
来实现此目的base::BindStateBase*
。只要此BindStateBase
指针仅与存储的Run()
指针一起使用,这是安全的。
要base::BindState<>
在内部创建的对象base::Bind()
的功能。这些功能以及一组内部模板负责
- 将函数签名解压缩为返回类型和参数
- 确定绑定的参数数
- 创建存储绑定参数的BindState
- 执行编译时断言可避免容易出错的行为
- 返回一个
Callback<>
带有与未绑定参数的数量匹配的Arity的a ,并且如果我们绑定一个方法,该知道已知正确的目标对象的引用语义。
这些base::Bind
函数使用类型推断和可变参数模板执行上述操作。
默认情况下,base::Bind()
将存储所有绑定参数的副本,并且如果绑定的函数是类方法,则尝试引用目标对象。即使函数将参数作为const引用,也会创建这些副本。(禁止绑定到非const引用,请参见bind.h。)
为了改变这种行为,我们引入了一组参数包装器(例如 base::Unretained()
)。这些是按值传递的简单容器模板,并包装指向参数的指针。有关更多信息,请参见base / bind_helpers.h中的文件级注释。
这些类型将传递给Unwrap()
函数以修改的行为 base::Bind()
。这些Unwrap()
函数通过根据参数是否为包装器类型进行部分专业化来更改行为。
base::Unretained()
特定于铬。
缺少功能
- 将数组绑定到采用非常量指针的函数。例:
void Foo(const char* ptr);
void Bar(char* ptr);
base::Bind(&Foo, "test");
base::Bind(&Bar, "test"); // This fails because ptr is not const.
- 如果部分绑定参数,则可能在绑定参数之前具有未绑定参数。例:
void Foo(int x, bool y);
base::Bind(&Foo, _1, false); // _1 is a placeholder.
如果您想base::Callback
在自己的头文件中进行前向声明,请改为使用“ base / callback_forward.h”。
Chromium UI
概述和背景
Windows提供了非常原始的工具来构建用户界面。该系统提供了一些基本控件和本机窗口容器,但是构建自定义用户界面很困难。由于希望Chromium具有与众不同的美感,因此不得不在Windows上构建框架以加速自定义UI的开发。该系统称为视图(view)。
views*是一种渲染系统,与WebKit或Gecko中用于渲染网页的系统不同。用户界面由称为Views*的组件树构成。这些视图负责呈现,布局和事件处理。树中的每个视图代表UI的不同组件。类似物是HTML文档的层次结构。
View层次结构的根是Widget,它是本机窗口。本机窗口从Windows接收消息,将它们转换为View层次结构可以理解的内容,然后将它们传递给RootView 。然后,RootView 开始将事件传播到View层次结构中。
绘画和布局以类似的方式进行。视图树中的视图有其自身的界限(通常通过其包含的View的Layout方法插入其上),因此当要求其进行Paint绘制时,它会绘制成一个裁剪到其界限的画布,并将渲染的原点转换为视图的左上角。 接收到Paint消息时,将整个View树的渲染完成到由Widget设置并拥有的单个画布中。渲染本身是通过结合使用Skia和GDI调用完成的-GDI用于文本,而Skia用于其他所有内容。
但是,Chromium的UI中的多个UI控件未使用视图呈现。相反,它们是托管在一种特殊的视图中的本机Windows控件,该视图知道如何显示和调整本机小部件的大小。这些用于按钮,表格,单选按钮,复选框,文本字段和其他此类控件。由于它们使用本机控件,因此这些视图也不是特别可移植的,除了可能在API中。
除非平台特定的渲染代码,根据系统指标对内容进行大小调整的代码等等,否则View系统的其余部分并不是特别难以移植,因为大多数渲染都是使用跨平台Skia库完成的。出于历史原因,View的许多功能都采用Windows或ATL类型,但是自那时以来,我们使用许多与平台无关的类型增强了ui / gfx /,最终可以将其替换为这些类型。
代码位置和信息
视图提供的基本类和接口集可以在src / ui / views /目录中找到。所有基本视图类都在“ views ”命名空间中。
通用小部件
在视图框架中:
- WidgetWin**:视图中所有Widget的基类 。提供基本的子窗口控件实现。如果您不创建顶层窗口或对话框,则直接将其子类化。
- 窗口:顶级窗口。WidgetWin的子类。
有关使用Window,CustomFrameWindow等构建对话框和其他窗口式UI的更多信息,请阅读Viewing Windowing。
在Chromium浏览器前端中:
- **BrowserFrame: Window的子类,可为Chrome中的Browser窗口提供其他消息处理。请参阅 浏览器窗口。
- ConstrainedWindowImpl:Window的子类,为受约束的对话框(例如HTTP基本身份验证提示)提供框架。
其它方法
在项目开始时,我们开始使用本机窗口和许多Windows应用程序中使用的所有者绘制方法来构建Chromium浏览器。事实证明这是不令人满意的,因为本机窗口本身不支持透明性,并且处理事件需要繁琐的窗口子类化。一些早期的UI元素倾向于使用自定义绘画和事件处理(例如,自动完成),但这是基于情况的临时性。
Windows的现有UI工具包同样令人不满意,其部件集有限,外观不自然或编程模型笨拙。
局限性/问题
总的来说,视图使构建复杂的自定义UI相对容易。但是,它有一些粗糙的边缘可以随着时间的推移而得到改善:
- 目前,事件类型有时会出现问题-它们会破解本机Windows消息参数,然后将其丢弃。有时,此信息很有用。
- 一些点对点消息处理。
- 在将本机控件插入 具有有效HWND的附加到Widget的View层次结构中之前,它们无法正常工作。我们的许多本机控件都具有API方法,要求它们存在于窗口层次结构中。这意味着它们在插入之前无法完全初始化。最终将改进View API以使其更清晰(错误5191)。
- 基本的Widget界面本身在时间上有些冻结。进行一些改进和合并是值得的。
参考文献
- https://www.chromium.org/Home
- https://source.chromium.org/c...
- https://www.chromium.org/deve...
- https://www.chromium.org/deve...
- https://www.chromium.org/deve...
- https://source.chromium.org/c...
- https://www.chromium.org/deve...
- https://source.chromium.org/c...
- https://source.chromium.org/c...
- https://www.chromium.org/deve...
- https://www.chromium.org/deve...
- https://www.chromium.org/deve...
- https://www.chromium.org/deve...
- https://www.chromium.org/deve...
- https://www.chromium.org/omni...
ith Visual Studio is that both are
from Microsoft.
Here's what works well:
- Editing code works well especially when you get used to the [keyboard
shortcuts](https://code.visualstudio.com...
VS Code is very responsive and can handle even big code bases like Chromium. - Git integration is a blast. Built-in side-by-side view, local commit and
even extensions for
history
and
blame view. - Debugging works
well, even though startup times can be fairly high (~40 seconds with
gdb on Linux, much lower on Windows). You can step through code, inspect
variables, view call stacks for multiple threads etc. - Opening files and searching solution-wide works well now after having
problems in earlier versions. - Building works well. Build tools are easy to integrate. Warnings and errors
are displayed on a separate page and you can click to jump to the
corresponding line of code. - VSCode Remote, which allows you to edit remotely-hosted code, and even run
computationally expensive plugins like vscode-clangd on the remote
server/workstation (see the Remote section). Great for working-
from-home. (Googlers: See go/vscode-remote].)
[TOC]
Updating This Page
Please keep this doc up-to-date. VS Code is still in active development and
subject to changes. This doc is checked into the Chromium git repo, so if you
make changes, read the [documentation
guidelines](https://chromium.googlesource...
and submit a change list.
All file paths and commands have been tested on Linux. Windows and Mac might
require a slightly different setup (e.g. Ctrl
-> Cmd
). Please update this
page accordingly.
Setup
Installation
Follow the steps on https://code.visualstudio.com... To
run it on Linux, just navigate to chromium/src
folder and type code .
in a
terminal. The argument to code
is the base directory of the workspace. VS
Code does not require project or solution files. However, it does store
workspace settings in a .vscode
folder in your base directory.
Fixes for Known Issues
Git on Windows
If you only have the depot_tools
Git installed on your machine, even though it
is in your PATH, VS Code will ignore it as it seems to be looking for git.exe
.
You will have to add the following to your settings in order for the Git
integration to work:
{
"git.path": "C:\\src\\depot_tools\\git.bat"
}
Rendering of underscore on Linux
As mentioned in #35901, VS
Code will not show underscore (_
) properly on Linux by default. You can work
around this issue by forcing another font such as the default monospace
or
changing the font size in your settings:
{
// If you want to use the default "monospace" font:
//"terminal.integrated.fontFamily": "monospace"
// If you would rather just increase the size of the font:
//"terminal.integrated.fontSize": 15
// If you would rather decrease the size of the font:
//"terminal.integrated.fontSize": 13
}
Useful Extensions
Up to now, you have a basic version of VS Code without much language support.
Next, we will install some useful extensions. Jump to the extensions window
(Ctrl+Shift+X
) and install these extensions, you will most likely use them
every day:
- C/C++ -
Code formatting, debugging, Intellisense. Enables the use of clang-format
(via theC_Cpp.clang_format_path
setting) and format-on-save (via theeditor.formatOnSave
setting). - Python -
Linting, intellisense, code formatting, refactoring, debugging, snippets. - Toggle Header/Source -
Toggles between .cc and .h withF4
. The C/C++ extension supports this as
well throughAlt+O
but sometimes chooses the wrong file when there are
multiple files in the workspace that have the same name. - Protobuf support -
Syntax highlighting for .proto files. - Mojom IDL support -
Syntax highlighting and a
language server
for .mojom files. This isn't available on the VS Code marketplace for now.
You need to install it manually. - vscode-clangd -
If you do not plan to use VSCode for debugging, vscode-clangd is a great
alternative to C/C++ IntelliSense. It knows about how to compile Chromium,
enabling it to provide smarter autocomplete than C/C++ IntelliSense as well
as allowing you to jump from functions to their definitions. See
clangd.md for setup instructions.
If you need to debug, enable C/C++ extension but set "C_Cpp: Intelli Sense Engine" to disabled,
and restart VSCode. - Rewrap -
Wrap lines at 80 characters withAlt+Q
. - Remote -
Remotely connect to your workstation through SSH using your laptop. See the
Remote section for more information about how to set this up.
The following extensions might be useful for you as well:
- Annotator -
Git blame view. - Git History (git log) -
Git history view. - chromium-codesearch -
Mac and Linux only: adds ability to open the current line in [Chromium Code
Search](https://cs.chromium.org/). All other functionality is deprecated, so
currently only of limited usefulness. - change-case -
Quickly change the case of the current selection or current word. - Instant Markdown -
Instant markdown (.md) preview in your browser as you type. This document
was written with this extension! - you-complete-me -
Alternative autocomplete extension. Can be configured to use a variety of
language servers, so helpful if not using clangd for code completion.
See You-Complete-Me extension setup
for additional setup instructions.
Also be sure to take a look at the
VS Code marketplace to check out
other useful extensions.
Color Scheme
Press Ctrl+Shift+P, color, Enter
to pick a color scheme for the editor. There
are also tons of [color schemes available for download on the
marketplace](https://marketplace.visualstu...
Usage Tips
Ctrl+P
opens a search box to find and open a file.F1
orCtrl+Shift+P
opens a search box to find a command (e.g. Tasks: Run
Task).Ctrl+K, Ctrl+S
opens the key bindings editor.Ctrl+`
toggles the built-in terminal.Ctrl+Shift+M
toggles the problems view (linter warnings, compile errors
and warnings). You'll swicth a lot between terminal and problem view during
compilation.Alt+O
switches between the source/header file.Ctrl+G
jumps to a line.F12
jumps to the definition of the symbol at the cursor (also available on
right-click context menu).Shift+F12
orF1, CodeSearchReferences, Return
shows all references of
the symbol at the cursor.F1, CodeSearchOpen, Return
opens the current file in Code Search.Ctrl+D
selects the word at the cursor. Pressing it multiple times
multi-selects the next occurrences, so typing in one types in all of them,
andCtrl+U
deselects the last occurrence.Ctrl+K, Z
enters Zen Mode, a fullscreen editing mode with nothing but the
current editor visible.Ctrl+X
without anything selected cuts the current line.Ctrl+V
pastes
the line.
Java/Android Support
To get Java support in VS Code, you'll need to install the
'Java Extension Pack' extension, but you'll want to immediately uninstall or
disable the Maven for Java extension so it stops nagging you as we won't need
it.
Setting up code completion/reference finding/etc.
You'll need to generate a placeholder .classpath file and locate it. In order
to generate it, right click on any Java source folder in the left panel and
choose "Add folder to java source path". Its location will depend on whether
you're doing local or remote development. Local path on linux will look
something like:
~/.vscode/data/User/workspaceStorage/<hash>/redhat.java/jdt_ws/<project>/.classpath
You might find multiple folders when looking for <project>
. Choose anything exceptjdt.ls-java-project
. If you only see jdt.ls-java-project
, try using the
"Add folder to java source path" option again.
If doing remote development, the file will be under ~/.vscode-server/
on your
remote machine.
You'll need to replace all of the contents of that file with the contents oftools/android/eclipse/.classpath
(external) orclank/development/ide/eclipse/.classpath
(generated by gclient runhooks for
Chrome developers), and then replace some paths as vscode interprets some paths
differently from eclipse.
Replace:
kind="src" path="
withkind="src" path="_/
- eg.
<classpathentry kind="src" path="_/android_webview/glue/java/src"/>
- eg.
Replace:
kind="lib" path="../src
withkind="lib" path="_
- eg.
<classpathentry kind="lib" path="_/out/Debug/lib.java/base/base_java.jar"/>
- eg.
Remove all nested paths (or exclude them from their parents). At time of
writing:third_party/android_protobuf/src/java/src/main/java
third_party/junit/src/src/main/java
Also, make sureexport ANDROID_HOME=/usr/local/google/home/{your_ldap}/Android/Sdk
is in the
remote machine's ~/.bashrc
.
Then restart vscode, open a Java file, and wait for a bit.
Debugging tips:
Right clicking on a folder in vscode and clicking "Add folder to java source
path" will error if there are syntax problems with your classpath. (Don't use
this actually add new paths to your classpath as it won't work correctly)- If there are no syntax errors, ensure the correct .classpath file is being
used by seeing if the folder was actually added to the .classpath file you
edited.
- If there are no syntax errors, ensure the correct .classpath file is being
Setup For Chromium
VS Code is configured via JSON files. This paragraph contains JSON configuration
files that are useful for Chromium development, in particular. See [VS Code
documentation](https://code.visualstudio.com... for an
introduction to VS Code customization.
Workspace Settings
Open the file //tools/vscode/settings.json5,
and check out the default settings there. Feel free to commit added or removed
settings to enable better team development, or change settings locally to suit
personal preference. Remember to replace <full_path_to_your_home>
! To use
these settings wholesale, enter the following commands into your terminal while
at the src directory:
$ mkdir .vscode/
$ cp tools/vscode/settings.json5 .vscode/settings.json
Note: these settings assume that the workspace folder (the root folder displayed
in the Explorer tab) is chromium/src. If this is not the case, replace any
references to ${workspaceFolder} with the path to chromium/src.
Tasks
Next, we'll tell VS Code how to compile our code, run tests, and to read
warnings and errors from the build output. Open the file
//tools/vscode/tasks.json5. This will provide tasks
to do basic things. You might have to adjust the commands to your situation and
needs. To use these settings wholesale, enter the following command into your
terminal:
$ cp tools/vscode/tasks.json5 .vscode/tasks.json
Launch Commands
Launch commands are the equivalent of F5
in Visual Studio: They launch some
program or a debugger. Optionally, they can run some task defined intasks.json
. Launch commands can be run from the debug view (Ctrl+Shift+D
).
Open the file at //tools/vscode/launch.json5 and
adjust the example launch commands to your situation and needs (e.g., the value
of "type" needs adjustment for Windows). To use these settings wholesale, enter
the following command into your terminal:
$ cp tools/vscode/launch.json5 .vscode/launch.json
Key Bindings
To edit key bindings, press Ctrl+K, Ctrl+S
. You'll see the defaults on the
left and your overrides on the right stored in the file keybindings.json
. To
change a key binding, copy the corresponding key binding to the right. It's
fairly self-explanatory.
You can bind any command to a key, even commands specified by extensions likeCodeSearchOpen
. For instance, to bind CodeSearchOpen
to F2
to , simply add{ "key": "F2", "command": "cs.open" },
.
Note that the command title CodeSearchOpen
won't work. You have to get the
actual command name from the [package.json
file](https://github.com/chaopeng/v...
of the extension.
If you are used to other editors, you can also install your favorite keymap.
For instance, to install eclipse keymaps, install thevscode-eclipse-keybindings
extension. More keymaps can be found
in the marketplace.
Some key bindings that are likely to be useful for you are available at
//tools/vscode/keybindings.json5. Please
take a look and adjust them to your situation and needs. To use these settings
wholesale, enter the following command into your terminal:
$ cp tools/vscode/keybindings.json5 .vscode/keybindings.json
Remote
VSCode now has a
Remote framework
that allows you to use VSCode on your laptop while your code is hosted
elsewhere. This really shines when used in conjunction with the vscode-clangd plugin,
which allows clangd to run remotely as well.
To get this to run, install the Remote pack extension, and then make sure your
ssh config file has your remote connection:
~/.ssh/config
:
Host my-connection
HostName my-remote-host.corp.company.com
VSCode will then list this connection in the 'Remote Explorer' section on the
left. To launch VSCode with this connection, click on the '+window' icon next
to the listed hostname. It has you choose a folder - use the 'src' folder root.
This will open a new VSCode window in 'Remote' mode. *Now you can install
extensions specifically for your remote connection, like vscode-clangd, etc.*
Chromebooks
For Googlers, here are
Google-specific instructions for setting up remote development on chromebooks
without using Crostini.
Windows & SSH
This currently is difficult on Windows because VSCode remote tools assumes
'sshd' is installed, which isn't the case on Windows. If someone figures out
how to get vscode remote working on windows with ssh please update this
document :)
Snippets
There are some useful snippets provided in
//tools/vscode/cpp.json5.
You can either install them in your user profile (path may vary depending on the
platform):
$ cp tools/vscode/cpp.json5 ~/.config/Code/User/snippets/cpp.json
Or install them as project snippets after installing the [Project
Snippets](https://marketplace.visualstu...
extension:
$ cp tools/vscode/cpp.json5 .vscode/snippets/cpp.json
Tips
The out
folder
Automatically generated code is put into a subfolder of out/, which means that
these files are ignored by VS Code (see files.exclude above) and cannot be
opened e.g. from quick-open (Ctrl+P
).
As of version 1.21, VS Code does not support negated glob commands, but you can
define a set of exclude pattern to include only out/Debug/gen:
"files.exclude": {
// Ignore build output folders. Except out/Debug/gen/
"out/[^D]*/": true,
"out/Debug/[^g]*": true,
"out/Debug/g[^e]*": true,
"out_*/**": true,
},
Once it does, you can use
"!out/Debug/gen/**": true
in files.exclude instead of the symlink.
Using VS Code as git editor
Add [core] editor = "code --wait"
to your ~/.gitconfig
file in order to use
VS Code as editor for git commit messages etc. Note that the editor starts up
significantly slower than nano or vim. To use VS Code as merge tool, add[merge] tool = code
.
Task Names
Note that we named the tasks 1-build_chrome_debug
, 2-build_chrome_release
etc. This allows you to quickly execute tasks by pressing their number:
Press Ctrl+P
and enter task <n>
, where <n>
is the number of the task. You
can also create a keyboard shortcut for running a task. `File > Preferences >
Keyboard Shortcuts and add
{ "key": "ctrl+r", "command":
"workbench.action.tasks.runTask", "when": "!inDebugMode" }`. Then it's
sufficient to press Ctrl+R
and enter <n>
.
Working on Laptop
Because autocomplete is provided by the You-Complete-Me extension, consider
disabling C/C++ autocomplete and indexing to save battery. In addition, you
might want to disable git status autorefresh as well.
"git.autorefresh": false,
"C_Cpp.autocomplete": "Disabled",
Unable to open $File resource is not available when debugging Chromium on Linux
Chromium recently changed
the file path to be relative to the output dir. Checkgn args out/$dir --list
if strip_absolute_paths_from_debug_symbols
is true (which is the default),
set cwd
to the output dir. otherwise, set cwd
to ${workspaceRoot}
.
You-Complete-Me extension setup
If using the You-Complete-Me extension, complete its installation by entering
these commands in a terminal:
$ git clone https://github.com/Valloric/ycmd.git ~/.ycmd
$ cd ~/.ycmd
$ git submodule update --init --recursive
$ ./build.py --clang-completer
If it fails with "Your C++ compiler does NOT fully support C++11." but you know
you have a good compiler, hack cpp/CMakeLists.txt to set CPP11_AVAILABLE true.
On Mac, replace the last command above with the following.
$ ./build.py --clang-completer --system-libclang
On Windows, if depot_tools' Python is the only one installed, a separate Python
3 install is needed. The last command should then be run as follows.
> <Python 3 directory>/python.exe build.py --clang-completer
More
More tips and tricks can be found
here.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。