Chrome多线程任务处理

Posted 知识店铺

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Chrome多线程任务处理相关的知识,希望对你有一定的参考价值。

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-a base::TaskRunner
  • 单线程任务运行程序(Single-thread task runnet):顺序任务运行程序,可确保所有任务将由同一物理线程处理。在Chrome中,这是 base::SingleThreadTaskRunner-a base::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::TaskRunnerbase::PostTask*()直接调用的替代方法。当事先不知道任务是并行,按顺序还是单线程发布时,这尤其有用(请参阅发布序列化任务,将多个任务发布到同一线程)。因为base::TaskRunnerbase::SequencedTaskRunnerand 的基类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::CurrentThreadtrait。

// 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::SequencedTaskRunnerbase::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::ThreadTaskRunnerHandlebase::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::WeakPtrs的类方法回调只能在销毁对象的相同序列上运行,因为否则回调的执行可能会与对象的删除竞争。

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()在拆卸期间,则将不会实际执行它们。

如果MyClassbase::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::Closurebase::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::refstd::cref存储引用原始参数的(常量)引用。这意味着您必须确保对象的寿命超过了回调!

实施说明

该设计来自何处:

的设计base::Callbackbase::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::Unretainedbase::Ownedbase::RetainedRefbase::Passed

实施工作原理:

系统包含三个主要组件:

  1. base::Callback<>班。
  2. base::Bind()功能。
  3. 参数包装器(例如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界面本身在时间上有些冻结。进行一些改进和合并是值得的。

参考文献

  1. https://www.chromium.org/Home
  2. https://source.chromium.org/c...
  3. https://www.chromium.org/deve...
  4. https://www.chromium.org/deve...
  5. https://www.chromium.org/deve...
  6. https://source.chromium.org/c...
  7. https://www.chromium.org/deve...
  8. https://source.chromium.org/c...
  9. https://source.chromium.org/c...
  10. https://www.chromium.org/deve...
  11. https://www.chromium.org/deve...
  12. https://www.chromium.org/deve...
  13. https://www.chromium.org/deve...
  14. https://www.chromium.org/deve...
  15. 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: