Chrome 開發中的任務調度與線程模型實戰指南

內容

  • 概述
    • 快速入門指南
  • 核心概念
  • 線程詞典
    • 線程
    • 任務
    • 優先使用序列而不是物理線程
  • 發布并行任務
    • 直接發布到線程池
    • 通過 TaskRunner 發布
  • 發布順序任務
    • 發布到新序列
    • 發布到當前(虛擬)主題
  • 使用序列代替鎖
  • 將多個任務發布到同一線程
    • 發布到瀏覽器進程中的主線程或 IO 線程
    • 發送到渲染進程的主線程
    • 發布到自定義 SingleThreadTaskRunner
    • 發帖至當前主題
  • 將任務發布到 COM 單線程單元 (STA) 線程 (Windows)
  • 已發布任務的內存排序保證
  • 使用 TaskTraits 注釋任務
  • 保持瀏覽器響應
  • 延遲發布任務
    • 延遲發布一次性任務
    • 延遲發布重復任務
  • 取消任務
    • 使用 base::WeakPtr
    • 使用 base::CancelableTaskTracker
  • 發布并行運行的作業
    • 向正在運行的作業添加額外工作
  • 測試
  • 在新進程中使用線程池
  • TaskRunner 所有權(鼓勵不依賴注入)
  • 常問問題
  • 內部
    • 序列管理器
    • 消息泵
    • 運行循環
    • 任務重入
  • 通用 API
    • SingleThreadTaskExecutor 和 TaskEnvironment
  • MessageLoop 和 MessageLoopCurrent

注意:有關更多示例,請參閱線程和任務常見問題解答。

概述

Chrome 采用多進程架構,每個進程都高度多線程化。本文檔將介紹每個進程共享的基本線程系統。我們的主要目標是保持瀏覽器的高響應速度。在沒有外部延遲或工作負載要求的情況下,Chrome 致力于成為一個高并發系統,但不一定是并行系統。

關于 Chromium 并發(尤其是序列)方式的基本介紹可以在這里找到。

本文檔假設您熟悉計算機科學線程概念。

快速入門指南

  • 不要在主線程(即瀏覽器進程中的“UI”線程)或 IO 線程(每個進程用于接收 IPC 的線程)上執行昂貴的計算或阻塞 IO。繁忙的 UI / IO 線程可能會導致用戶可見的延遲,因此最好將這些工作放在線程池中運行。
  • 始終避免從不同的線程或序列讀取/寫入內存中的同一位置。這會導致數據爭用!建議跨序列傳遞消息。不建議使用鎖等消息傳遞的替代方案。
  • 如果您需要協調存在于不同序列中的多個對象,請注意對象的生命周期。
    • 為了防止意外的數據爭用,大多數類最好只用于單個序列。您應該使用SEQUENCE_CHECKER或base::SequenceBound等實用程序來幫助強制執行此約束。
    • 根據經驗法則,避免使用base::Unretained。弱指針通常可以被替代。
    • std::unique_ptr最好通過以下方式明確所有權。
    • scoped_refptrs可用于跨多個序列擁有多個所有者的對象。這通常是錯誤的設計模式,不建議在新代碼中使用。

核心概念

  • 任務:待處理的工作單元。實際上是一個帶有可選關聯狀態的函數指針。在 Chrome 中,它base::OnceCallback分別通過和 來base::RepeatingCallback創建。(文檔)。base::BindOncebase::BindRepeating
  • 任務隊列:待處理的任務隊列。
  • 物理線程:操作系統提供的線程(例如 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::SequencedTaskRunnerwhich is-a?base::TaskRunner
  • 單線程任務運行器:一種順序任務運行器,保證所有任務都由同一個物理線程處理。在 Chrome 中,它就是base::SingleThreadTaskRunnerwhich is-a?base::SequencedTaskRunner。我們盡可能優先使用序列而不是線程。

線程詞典

讀者須知:以下術語旨在彌合常見線程命名法與我們在 Chrome 中使用它們的方式之間的差距。如果您剛開始學習,可能會覺得有點難懂。如果難以理解,請考慮跳至下文更詳細的部分,并在必要時再參考本部分。

  • 線程不安全:Chrome 中的絕大多數類型都是線程不安全的(設計如此)。訪問此類類型/方法必須進行外部同步。通常,線程不安全的類型要求所有訪問其狀態的任務都發布到同一個線程base::SequencedTaskRunner,并在調試版本中使用成員進行驗證SEQUENCE_CHECKER。鎖也是一種同步訪問的選項,但在 Chrome 中,我們強烈建議使用序列而不是鎖。
  • 線程親和性:此類類型/方法需要始終從同一物理線程(即從同一個base::SingleThreadTaskRunner)訪問,并且通常有一個THREAD_CHECKER成員來驗證它們是否屬于線程親和性。除非使用第三方 API 或具有線程親和性的葉子依賴項,否則在 Chrome 中,幾乎沒有理由讓類型成為線程親和性。請注意,base::SingleThreadTaskRunneris-abase::SequencedTaskRunner類型是線程不安全的子集。線程親和性有時也稱為線程敵對性
  • 線程安全:此類類型/方法可以安全地并行訪問。
  • 線程兼容:此類類型提供對 const 方法的安全并行訪問,但對非常量(或混合 const/非常量訪問)需要同步。Chrome 不提供讀寫鎖;因此,唯一的用例是對象(通常是全局變量),它們以線程安全的方式初始化一次(可以在啟動的單線程階段初始化,也可以通過線程安全的靜態局部初始化范式延遲初始化base::NoDestructor),并且此后永遠不可變。
  • 不可變:線程兼容類型的子集,構造后無法修改。
  • 序列友好:此類類型/方法是線程非安全類型,支持從 調用base::SequencedTaskRunner。理想情況下,所有線程非安全類型都應該如此,但遺留代碼有時會在單純的線程非安全場景中強制執行線程親和性檢查,從而過度嚴格。有關更多詳細信息,請參閱下文的“優先使用序列而非線程” 。

線程

每個 Chrome 進程都有

  • 主線程
    • 在瀏覽器進程中(BrowserThread::UI):更新UI
    • 在渲染器進程(Blink 主線程)中:運行大部分 Blink 功能
  • IO線程
    • 在所有進程中:所有 IPC 消息都到達此線程。處理該消息的應用程序邏輯可能位于不同的線程中(例如,IO 線程可能會將消息路由到綁定到不同線程的Mojo 接口)。
    • 更一般地,大多數異步 I/O 發生在這個線程上(例如,通過 base::FileDescriptorWatcher)。
    • 在瀏覽器進程中:這被稱為BrowserThread::IO。
  • 一些特殊用途的線程
  • 以及一個通用線程池

大多數線程都有一個循環,從隊列中獲取任務并運行它們(該隊列可能由多個線程共享)。

任務

任務被base::OnceClosure添加到隊列中以便異步執行。

Abase::OnceClosure存儲一個函數指針和參數。它有一個Run()方法,可以使用綁定的參數調用該函數指針。它是使用 創建的base::BindOnce。(參考Callback<> 和 Bind() 文檔)。

<span style="background-color:#fafafa"><span style="color:#000000">無效任務A(){}
無效 TaskB(int v){}自動任務_a = base::BindOnce(&TaskA);
自動任務b = base::BindOnce(&TaskB,42);
</span></span>

一組任務可以通過以下方式之一執行:

  • 并行:沒有任務執行順序,可能在任何線程上同時執行
  • 順序:按發布順序執行任務,在任何線程上一次執行一個任務。
  • 單線程:按發布順序執行任務,一次在單個線程上執行一個任務。
    • COM 單線程:COM 初始化的單線程變體。

優先使用序列而不是物理線程

強烈建議在虛擬線程上進行順序執行,而不是在物理線程上進行單線程執行。除了綁定到主線程 (UI) 或 IO 線程的類型/方法外,base::SequencedTaskRunner通過管理自己的物理線程(參見下文的“發布順序任務”)可以更好地實現線程安全。

為“當前物理線程”公開的所有 API 都具有與“當前序列”等效的 API(映射)。

如果您發現自己編寫了一個序列友好的類型,但它THREAD_CHECKER在葉子依賴項中未通過線程親和性檢查(例如),請考慮將該依賴項也設置為序列友好的。Chrome 中的大多數核心 API 都是序列友好的,但某些遺留類型可能仍然過度使用 ThreadChecker/SingleThreadTaskRunner,而實際上它們可以依賴于“當前序列”,不再是線程親和性的。

發布并行任務

直接發布到線程池

可以運行在任何線程上并且與其他任務沒有排序或互斥要求的任務應該使用base::ThreadPool::PostTask*()中定義的函數之一來發布base/task/thread_pool.h。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">));</span>
</span></span>

這會發布具有默認特征的任務。

這些base::ThreadPool::PostTask*()函數允許調用者通過 TaskTraits 提供有關任務的更多詳細信息(參考使用 TaskTraits 注釋任務)。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">,</span><span style="color:#660066">MayBlock </span><span style="color:#666600">()},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">));</span>  
</span></span>

通過 TaskRunner 發布

并行base::TaskRunner是直接調用的替代方案base::ThreadPool::PostTask*()。這主要適用于無法預先知道任務是并行、順序還是單線程發送的情況(參見發送順序任務、將多個任務發送到同一線程)。由于是和base::TaskRunner的基類,因此成員可以包含、或。base::SequencedTaskRunnerbase::SingleThreadTaskRunnerscoped_refptr<TaskRunner>base::TaskRunnerbase::SequencedTaskRunnerbase::SingleThreadTaskRunner

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">類</span><span style="color:#000000">A </span><span style="color:#666600">{ </span><span style="color:#000088">public </span><span style="color:#666600">: </span><span style="color:#000000">A </span><span style="color:#666600">() </span><span style="color:#666600">= </span><span style="color:#000088">default </span><span style="color:#666600">;</span><span style="color:#000088">void </span><span style="color:#660066">PostSomething </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#000000">task_runner_ </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">A </span><span style="color:#666600">,</span><span style="color:#666600">& </span><span style="color:#660066">DoSomething </span><span style="color:#666600">)); </span><span style="color:#666600">}</span>   <span style="color:#000088">無效的</span><span style="color:#660066">執行某事</span><span style="color:#666600">()</span><span style="color:#666600">{ </span><span style="color:#666600">}</span>  <span style="color:#000088">私有</span><span style="color:#666600">:</span><span style="color:#000000">scoped_refptr </span><span style="color:#666600">< </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskRunner </span><span style="color:#666600">> </span><span style="color:#000000">task_runner_ </span><span style="color:#666600">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateTaskRunner </span><span style="color:#666600">({ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">USER_VISIBLE </span><span style="color:#666600">}); </span><span style="color:#666600">};</span></span></span>

除非測試需要精確控制任務的執行方式,否則最好base::ThreadPool::PostTask*()直接調用(參考測試以了解在測試中以較少侵入的方式控制任務)。

發布順序任務

序列是一組按發布順序逐個運行的任務(不一定在同一線程中)。要將任務發布為序列的一部分,請使用base::SequencedTaskRunner。

發布到新序列

base::SequencedTaskRunner可以通過 創建A?base::ThreadPool::CreateSequencedTaskRunner()

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">scoped_refptr </span><span style="color:#666600"><SequencedTaskRunner> </span><span style="color:#660066">sequenced_task_runner </span><span style="color:#000000">= </span><span style="color:#666600">base </span><span style="color:#000000">:: </span><span style="color:#666600">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSequencedTaskRunner </span><span style="color:#666600">(... </span><span style="color:#666600">)</span><span style="color:#660066">;</span><span style="color:#880000">// TaskB 在 TaskA 完成后運行。</span><span style="color:#000000">
sequenced_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskA </span><span style="color:#666600">)); </span><span style="color:#000000">
sequenced_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskB </span><span style="color:#666600">));</span>
</span></span>

發布到當前(虛擬)主題

發布到當前(虛擬)線程的首選方式是通過base::SequencedTaskRunner::GetCurrentDefault()

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// 該任務將在當前(虛擬)線程的默認任務隊列上運行。base </span><span style="color:#000000">
:: </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">( </span><span style="color:#666600">& </span><span style="color:#660066">Task </span><span style="color:#666600">));</span>
</span></span>

請注意,SequencedTaskRunner::GetCurrentDefault()返回的是當前虛擬線程的默認隊列。在具有多個任務隊列的線程(例如 BrowserThread::UI)中,該隊列可能與當前任務所屬的隊列不同。“當前”任務運行器有意不通過靜態 getter 公開。您要么已經知道它并可以直接向其發送消息,要么不知道,唯一合理的目的地是默認隊列。有關詳細討論,請參閱https://bit.ly/3JvCLsX 。

使用序列代替鎖

Chrome 不鼓勵使用鎖。序列本身就提供了線程安全。建議您使用始終從同一序列訪問的類,而不是使用鎖來管理您自己的線程安全。

線程安全但不線程親和;這是為什么呢?發送到同一序列的任務將按順序運行。一個已排序的任務完成后,下一個任務可能會被另一個工作線程執行,但該任務肯定會受到前一個任務對其序列造成的任何副作用的影響。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">class </span><span style="color:#000000">A </span><span style="color:#666600">{ </span><span style="color:#000088">public </span><span style="color:#666600">: </span><span style="color:#000000">A </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// 不需要訪問創建序列。DETACH_FROM_SEQUENCE </span><span style="color:#666600">( </span><span style="color:#000000">sequence_checker_ </span><span style="color:#000000">) </span><span style="color:#666600">; </span><span style="color:#666600">}</span><span style="color:#000088">void </span><span style="color:#660066">AddValue </span><span style="color:#666600">( </span><span style="color:#660066">int </span><span style="color:#000000">v </span><span style="color:#666600">) </span><span style="color:#666600">{ </span><span style="color:#880000">// 檢查所有訪問是否在同一個序列上。DCHECK_CALLED_ON_VALID_SEQUENCE </span><span style="color:#666600">( </span><span style="color:#000000">sequence_checker_ </span><span style="color:#666600">); </span><span style="color:#000000">values_ </span><span style="color:#666600">. </span><span style="color:#000000">push_back </span><span style="color:#666600">( </span><span style="color:#000000">v </span><span style="color:#000000">) </span><span style="color:#666600">; </span><span style="color:#666600">}</span>  <span style="color:#000088">私有</span><span style="color:#666600">:</span><span style="color:#000000">SEQUENCE_CHECKER </span><span style="color:#666600">(</span><span style="color:#000000">sequence_checker_ </span><span style="color:#666600">);</span><span style="color:#880000">// 不需要鎖,因為所有訪問都在 // 同一個序列上</span><span style="color:#666600">。std </span><span style="color:#880000">:: </span><span style="color:#000000">vector </span><span style="color:#660066"><int> </span><span style="color:#008800">values_ </span><span style="color:#000000">; </span><span style="color:#666600">} </span><span style="color:#666600">;</span><span style="color:#000000">A a </span><span style="color:#666600">; </span><span style="color:#000000">
scoped_refptr </span><span style="color:#666600">< </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">> </span><span style="color:#000000">task_runner_for_a </span><span style="color:#666600">= </span><span style="color:#666600">...; </span><span style="color:#000000">
task_runner_for_a </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#660066">AddValue </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">Unretained </span><span style="color:#666600">(&a </span><span style="color:#000000">a </span><span style="color:#666600">), </span><span style="color:#006666">42 </span><span style="color:#666600">)); </span><span style="color:#000000">
task_runner_for_a </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#660066">AddValue </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">Unretained </span><span style="color:#666600">(&a </span><span style="color:#000000">a </span><span style="color:#666600">), </span><span style="color:#006666">27 </span><span style="color:#666600">));</span>   <span style="color:#880000">// 從不同的序列訪問會導致 DCHECK 失敗。scoped_refptr </span><span style="color:#666600"><SequencedTaskRunner> </span><span style="color:#660066">other_task_runner </span><span style="color:#666600">= </span><span style="color:#666600">...; </span><span style="color:#000000">other_task_runner </span><span style="color:#000000">
- </span><span style="color:#666600">> </span><span style="color:#000000">
PostTask </span><span style="color:#660066">( </span><span style="color:#666600">FROM_HERE </span><span style="color:#000000">, </span><span style="color:#666600">base </span><span style="color:#000000">:: </span><span style="color:#666600">BindOnce </span><span style="color:#660066">( </span><span style="color:#666600">& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#666600">AddValue </span><span style="color:#660066">, </span><span style="color:#666600">base </span><span style="color:#000000">:: </span><span style="color:#666600">Unretained </span><span style="color:#660066">( </span><span style="color:#666600">&a </span><span style="color:#000000">a </span><span style="color:#666600">), </span><span style="color:#006666">1 </span><span style="color:#666600">));</span>  
</span></span>

鎖應該僅用于交換可在多個線程上訪問的共享數據結構。如果一個線程基于昂貴的計算或通過磁盤訪問來更新該結構,那么這項緩慢的工作應該在不持有鎖的情況下完成。只有當結果可用時,才應該使用鎖來交換新數據。PluginList::LoadPlugins ( ) 中就有一個這樣的示例content/browser/plugin_list.cc。如果必須使用鎖,以下是一些最佳實踐和需要避免的陷阱。

為了編寫非阻塞代碼,Chrome 中的許多 API 都是異步的。通常,這意味著它們要么需要在特定的線程/序列上執行,并通過自定義委托接口返回結果,要么接受一個base::OnceCallback<>(或base::RepeatingCallback<>) 對象,該對象在請求的操作完成時被調用。上文 PostTask 部分介紹了如何在特定的線程/序列上執行工作。

將多個任務發布到同一線程

如果多個任務需要在同一個線程上運行,請將它們發布到base::SingleThreadTaskRunner。發布到同一個線程的所有任務將按照base::SingleThreadTaskRunner發布順序在同一個線程上運行。

發布到瀏覽器進程中的主線程或 IO 線程

要將任務發布到主線程或 IO 線程,請使用content::GetUIThreadTaskRunner({})content::GetIOThreadTaskRunner({})從content/public/browser/browser_thread.h

您可以向這些方法提供額外的 BrowserTaskTraits 作為參數,盡管這在 BrowserThreads 中通常并不常見,應該保留用于高級用例。

目前正在進行從以前的 base-API-with-traits 的遷移(任務 API v3 ),您可能仍會在整個代碼庫中找到它(它是等效的):

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{</span><span style="color:#000000">內容</span><span style="color:#666600">::</span><span style="color:#660066">瀏覽器線程</span><span style="color:#666600">:: </span><span style="color:#000000">UI </span><span style="color:#666600">},</span><span style="color:#666600">...);</span>  <span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSingleThreadTaskRunner </span><span style="color:#666600">({ </span><span style="color:#000000">content </span><span style="color:#666600">:: </span><span style="color:#660066">BrowserThread </span><span style="color:#666600">:: </span><span style="color:#000000">IO </span><span style="color:#666600">})</span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">...);</span></span></span>

注意:在遷移期間,很遺憾,您需要繼續手動包含content/public/browser/browser_task_traits.h. 以使用 browser_thread.h API。

主線程和 IO 線程已經非常繁忙。因此,盡可能將任務發布到通用線程(參見發布并行任務、發布順序任務)。將任務發布到主線程的良好理由是更新 UI 或訪問綁定到主線程的對象(例如Profile)。將任務發布到 IO 線程的良好理由是訪問綁定到主線程的組件內部(例如 IPC、網絡)。注意:無需顯式地將任務發布到 IO 線程即可發送/接收 IPC 或在網絡上發送/接收數據。

發送到渲染進程的主線程

TODO(blink-dev)

發布到自定義 SingleThreadTaskRunner

如果多個任務需要在同一個線程上運行,并且該線程不必是主線程或 IO 線程,則將它們發布到base::SingleThreadTaskRunner由...創建的base::Threadpool::CreateSingleThreadTaskRunner

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">scoped_refptr </span><span style="color:#666600"><SingleThreadTaskRunner> </span><span style="color:#660066">single_thread_task_runner </span><span style="color:#666600">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#666600">Threadpool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSingleThreadTaskRunner </span><span style="color:#666600">(... </span><span style="color:#660066">)</span><span style="color:#000000">;</span><span style="color:#880000">// TaskB 在 TaskA 完成后運行。兩個任務在同一個線程上運行。single_thread_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskA </span><span style="color:#666600">)); </span><span style="color:#000000">
single_thread_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskB </span><span style="color:#666600">)) </span><span style="color:#000000">
;</span>
</span></span>

請記住,我們更喜歡序列而不是物理線程,因此這很少是必要的。

發帖至當前主題

重要提示:要發布需要與當前任務序列互斥但并非必須在當前物理線程上運行的任務,請使用base::SequencedTaskRunner::GetCurrentDefault()而不是base::SingleThreadTaskRunner::GetCurrentDefault()(請參閱發布到當前序列)。這樣可以更好地記錄發布任務的要求,并避免不必要地使 API 依賴于物理線程。在單線程任務中,base::SequencedTaskRunner::GetCurrentDefault()相當于base::SingleThreadTaskRunner::GetCurrentDefault()

如果您仍然必須將任務發布到當前物理線程,請使用base::SingleThreadTaskRunner::CurrentDefaultHandle。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">
// 該任務</span><span style="color:#880000">將來會在當前線程上運行。base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">));</span>
</span></span>

將任務發布到 COM 單線程單元 (STA) 線程 (Windows)

需要在 COM 單線程單元 (STA) 線程上運行的任務必須發布到base::SingleThreadTaskRunner返回的base::ThreadPool::CreateCOMSTATaskRunner()。如將多個任務發布到同一線程中所述,所有發布到同一線程的任務均按base::SingleThreadTaskRunner發布順序在同一線程上運行。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// Task(A|B|C)UsingCOMSTA 將在同一個 COM STA 線程上運行。</span><span style="color:#000088">void </span><span style="color:#660066">TaskAUingCOMSTA </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// [這在 COM STA 線程上運行。]</span>  <span style="color:#880000">// 進行 COM STA 調用。</span><span style="color:#880000">// ...</span><span style="color:#666600">// 將另一個</span><span style="color:#880000">任務發布到當前 COM STA 線程。base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#000000">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskCUsingCOMSTA </span><span style="color:#666600">)); </span><span style="color:#666600">} </span><span style="color:#000088">void </span><span style="color:#660066">TaskBUsingCOMSTA </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#666600">} </span><span style="color:#000088">void </span><span style="color:#660066">TaskCUsingCOMSTA </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#666600">}</span><span style="color:#000088">自動</span><span style="color:#000000">com_sta_task_runner </span><span style="color:#666600">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateCOMSTATaskRunner </span><span style="color:#666600">(...); </span><span style="color:#000000">
com_sta_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(&</span><span style="color:#660066">TaskAUsingCOMSTA </span><span style="color:#666600">)); </span><span style="color:#000000">
com_sta_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(&</span><span style="color:#660066">TaskBUsingCOMSTA </span><span style="color:#666600">));</span>
</span></span>

已發布任務的內存排序保證

此任務系統保證在任務開始運行時,所有在發布任務之前順序執行的內存效應都對任務可見PostTask()。更正式地說,對的調用和發布任務的執行彼此之間存在先行發生關系::base。這適用于所有以 方式發布任務的變體,包括PostTaskAndReply()。同樣,對于作為同一 SequencedTaskRunner 的一部分按順序運行的任務,也存在先行發生關系。

了解這一保證至關重要,因為 Chrome 任務通常會訪問復制到 中的直接數據以外的內存base::OnceCallback,而這種先行關系可以避免任務本身內部的額外同步。舉一個非常具體的例子,考慮一個回調,它將一個指針綁定到剛剛在發布任務的線程中初始化的內存上。

一個更受約束的模型也值得注意。執行可以拆分成多個任務,分別運行在不同的任務運行器上,每個任務都以獨占方式訪問內存中的特定對象,無需顯式同步。發布另一個任務會將(對象的)“所有權”轉移給下一個任務。這樣,對象所有權的概念通常可以擴展到任務運行器的級別,從而提供有用的不變量來推理。該模型可以避免競爭條件,同時避免鎖和原子操作。由于其簡單性,該模型在 Chrome 中被廣泛使用。

使用 TaskTraits 注釋任務

base::TaskTraits封裝有關任務的信息,幫助線程池做出更好的調度決策。

base::TaskTraits當默認特征足夠時,可以傳遞需要的方法{}。默認特征適用于以下任務:

  • 不要阻塞(參考 MayBlock 和 WithBaseSyncPrimitives);
  • 與用戶阻止活動有關;(通過與執行該操作的組件建立排序依賴關系來明確或隱含地表示)
  • 可以阻止關機或在關機時跳過(線程池可以自由選擇合適的默認值)。

與該描述不匹配的任務必須使用明確的 TaskTraits 進行發布。

base/task/task_traits.h提供所有可用特征的詳盡文檔。內容層還提供了其他特征,content/public/browser/browser_task_traits.h以便于將任務發布到瀏覽器線程 (BrowserThread)。

下面是一些如何指定的示例base::TaskTraits

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// 此任務沒有顯式的 TaskTraits。它無法阻塞。其優先級為</span><span style="color:#880000">// USER_BLOCKING。它將阻塞關閉或在關閉時被跳過。base </span><span style="color:#660066">:: ThreadPool </span><span style="color:#666600">:: </span><span style="color:#000000">
PostTask </span><span style="color:#666600">( </span><span style="color:#660066">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#000000">BindOnce </span><span style="color:#666600">( </span><span style="color:#660066">... </span><span style="color:#666600">));</span><span style="color:#880000">// 此任務具有最高優先級。線程池將在</span><span style="color:#880000">USER_VISIBLE 和 BEST_EFFORT 任務之前調度它。base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#000000">
:: </span><span style="color:#000000">USER_BLOCKING </span><span style="color:#666600">} </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(...));</span><span style="color:#880000">// 此任務具有最低優先級,并允許阻塞(例如,它</span><span style="color:#880000">//可以從磁盤讀取文件)。base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#000000">
:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">MayBlock </span><span style="color:#666600">()},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(</span><span style="color:#666600">...));</span><span style="color:#880000">// 此任務會阻止關閉。進程在其執行完成</span><span style="color:#880000">之前不會退出。base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskShutdownBehavior </span><span style="color:#000000">
:: </span><span style="color:#000000">BLOCK_SHUTDOWN </span><span style="color:#666600">} </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(...));</span></span></span>

保持瀏覽器響應

不要在主線程、IO 線程或任何預期以低延遲運行任務的序列上執行高開銷工作。相反,請使用base::ThreadPool::PostTaskAndReply*()或異步執行高開銷工作base::SequencedTaskRunner::PostTaskAndReply()。請注意,IO 線程上的異步/重疊 I/O 是可以的。

示例:在主線程上運行下面的代碼將導致瀏覽器長時間無法響應用戶輸入。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// GetHistoryItemsFromDisk() 可能會阻塞很長時間。 // AddHistoryItemsToOmniboxDropDown( </span><span style="color:#660066">)</span><span style="color:#880000">更新 UI,因此必須</span><span style="color:#880000">// 在主線程上調用。AddHistoryItemsToOmniboxDropdown </span><span style="color:#666600">( </span><span style="color:#660066">GetHistoryItemsFromDisk </span><span style="color:#666600">( </span><span style="color:#008800">"keyword" </span><span style="color:#666600">));</span></span></span>

下面的代碼解決了這個問題,它先在線程池中調用 ,GetHistoryItemsFromDisk()然后在原始序列(本例中是主線程)中調用AddHistoryItemsToOmniboxDropdown()。第一次調用的返回值會自動作為第二次調用的參數。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTaskAndReplyWithResult </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">MayBlock </span><span style="color:#666600">()},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">GetHistoryItemsFromDisk </span><span style="color:#666600">,</span><span style="color:#008800">“關鍵字” </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">AddHistoryItemsToOmniboxDropdown </span><span style="color:#666600">));</span>  
</span></span>

延遲發布任務

延遲發布一次性任務

要發布延遲到期后必須運行一次的任務,請使用base::ThreadPool::PostDelayedTask*()base::TaskRunner::PostDelayedTask()

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostDelayedTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">Hours </span><span style="color:#666600">(</span><span style="color:#006666">1 </span><span style="color:#666600">));</span> <span style="color:#000000">scoped_refptr </span><span style="color:#666600">< </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">> </span><span style="color:#000000">task_runner </span><span style="color:#666600">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSequencedTaskRunner </span><span style="color:#666600">(</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">}); </span><span style="color:#000000">
task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostDelayedTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">Hours </span><span style="color:#666600">(</span><span style="color:#006666">1 </span><span style="color:#666600">));</span></span></span>
注意:延遲 1 小時的任務可能不必在延遲到期后立即運行。請指定此選項,base::TaskPriority::BEST_EFFORT以防止其在延遲到期時拖慢瀏覽器速度。

延遲發布重復任務

要發布必須定期運行的任務,請使用base::RepeatingTimer。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">class </span><span style="color:#000000">A </span><span style="color:#666600">{ </span><span style="color:#000088">public </span><span style="color:#666600">: </span><span style="color:#666600">~ </span><span style="color:#000000">A </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// 計時器在刪除時自動停止。</span><span style="color:#666600">} </span><span style="color:#000088">void </span><span style="color:#660066">StartDoingStuff </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#000000">timer_ </span><span style="color:#666600">. </span><span style="color:#660066">Start </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#660066">Seconds </span><span style="color:#666600">( </span><span style="color:#006666">1 </span><span style="color:#666600">), </span><span style="color:#000088">this </span><span style="color:#666600">, </span><span style="color:#666600">& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#660066">DoStuff </span><span style="color:#666600">); </span><span style="color:#666600">} </span><span style="color:#000088">void </span><span style="color:#660066">StopDoingStuff </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#000000">timer_ </span><span style="color:#666600">. </span><span style="color:#660066">Stop </span><span style="color:#666600">(); </span><span style="color:#666600">} </span><span style="color:#000088">private </span><span style="color:#666600">: </span><span style="color:#000088">void </span><span style="color:#660066">DoStuff </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// 在調用 StartDoingStuff() 的序列中每秒調用此方法</span><span style="color:#880000">。</span><span style="color:#666600">} </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">RepeatingTimer </span><span style="color:#000000">timer_ </span><span style="color:#666600">; </span><span style="color:#666600">};</span></span></span>

取消任務

使用 base::WeakPtr

base::WeakPtr可用于確保在對象被銷毀時取消綁定到該對象的任何回調。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#660066">int</span><span style="color:#660066">計算</span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#666600">… </span><span style="color:#666600">}</span>    <span style="color:#000088">class </span><span style="color:#000000">A </span><span style="color:#666600">{ </span><span style="color:#000088">public </span><span style="color:#666600">: </span><span style="color:#000088">void </span><span style="color:#660066">ComputeAndStore </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// 在線程池中安排對 Compute() 的調用,然后</span><span style="color:#880000">在當前序列中調用 A::Store()。對 // A::Store() 的調用</span><span style="color:#880000">在 |weak_ptr_factory_| 被銷毀時被取消。</span><span style="color:#880000">// (保證 |this| 不會被釋放后使用)。base </span><span style="color:#666600">:: </span><span style="color:#000000">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTaskAndReplyWithResult </span><span style="color:#660066">( </span><span style="color:#666600">FROM_HERE </span><span style="color:#000000">, </span><span style="color:#666600">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Compute </span><span style="color:#666600">), </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#000000">( </span><span style="color:#666600">& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#660066">Store </span><span style="color:#666600">, </span><span style="color:#000000">weak_ptr_factory_ </span><span style="color:#666600">. </span><span style="color:#660066">GetWeakPtr </span><span style="color:#666600">())); </span><span style="color:#666600">}</span><span style="color:#000088">私有</span><span style="color:#666600">:</span><span style="color:#000088">void</span><span style="color:#660066">存儲</span><span style="color:#666600">(</span><span style="color:#660066">int</span><span style="color:#000000">值</span><span style="color:#666600">)</span><span style="color:#666600">{ </span><span style="color:#000000">value_ </span><span style="color:#666600">= </span><span style="color:#000000">value </span><span style="color:#666600">; </span><span style="color:#666600">}</span><span style="color:#660066">int </span><span style="color:#000000">value_ </span><span style="color:#666600">; </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">WeakPtrFactory </span><span style="color:#666600"><A> </span><span style="color:#666600">weak_ptr_factory_ </span><span style="color:#000000">{ </span><span style="color:#000000">this </span><span style="color:#000088">} </span><span style="color:#666600">; </span><span style="color:#666600">} </span><span style="color:#666600">;</span></span></span>

注意:WeakPtr不是線程安全的:~WeakPtrFactory()并且Store()(綁定到WeakPtr)必須全部在同一個序列上運行。

使用 base::CancelableTaskTracker

base::CancelableTaskTracker允許以與任務運行順序不同的順序取消任務。請記住,CancelableTaskTracker無法取消已經開始運行的任務。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">auto </span><span style="color:#000000">task_runner </span><span style="color:#666600">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateTaskRunner </span><span style="color:#666600">({}); </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">CancelableTaskTracker </span><span style="color:#000000">cancellationable_task_tracker </span><span style="color:#666600">; </span><span style="color:#000000">
cancellationable_task_tracker.PostTask </span><span style="color:#666600">( </span><span style="color:#660066">task_runner.get </span><span style="color:#666600">(), </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">DoNothing </span><span style="color:#666600">()); // 僅</span><span style="color:#660066">當Task( </span><span style="color:#000000">)</span><span style="color:#880000">尚未開始運行時才</span><span style="color:#000000">取消</span><span style="color:#000000">
它</span><span style="color:#666600">。cancelable_task_tracker.TryCancelAll </span><span style="color:#666600">( </span><span style="color:#666600">) </span><span style="color:#666600">;</span></span></span>

發布并行運行的作業

這base::PostJob是一個高級用戶 API,能夠調度單個 base::RepeatingCallback 工作任務,并請求 ThreadPool 工作線程并行調用該任務。這可以避免以下情況:

  • 調用PostTask()每個工作項,造成大量開銷。
  • 固定數量的PostTask()調用會拆分工作,并且可能運行很長時間。當許多組件發布“核心數”任務,并且所有組件都希望使用所有核心時,這會帶來問題。在這種情況下,調度程序缺乏上下文,無法公平地處理多個相同優先級的請求,并且/或者無法在高優先級工作到來時請求低優先級工作讓步。

請參閱base/task/job_perftest.cc完整的示例。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// |worker_task| 的規范實現。void </span><span style="color:#000088">WorkerTask </span><span style="color:#666600">( </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">JobDelegate </span><span style="color:#666600">* </span><span style="color:#000000">job_delegate </span><span style="color:#666600">) </span><span style="color:#666600">{ </span><span style="color:#000088">while </span><span style="color:#666600">(! </span><span style="color:#000000">job_delegate </span><span style="color:#666600">-> </span><span style="color:#660066">ShouldYield </span><span style="color:#666600">()) </span><span style="color:#666600">{ </span><span style="color:#000088">auto </span><span style="color:#000000">work_item </span><span style="color:#666600">= </span><span style="color:#660066">TakeWorkItem </span><span style="color:#666600">(); </span><span style="color:#880000">// 最小工作單位。if </span><span style="color:#000088">( </span><span style="color:#666600">! </span><span style="color:#000000">work_item </span><span style="color:#666600">) </span><span style="color:#000088">return </span><span style="color:#660066">: </span><span style="color:#666600">ProcessWork </span><span style="color:#666600">( </span><span style="color:#000000">work_item </span><span style="color:#660066">) </span><span style="color:#666600">; </span><span style="color:#666600">} </span><span style="color:#666600">}</span><span style="color:#880000">// 返回最新的線程安全未完成工作項數量。</span><span style="color:#000088">void </span><span style="color:#660066">NumIncompleteWorkItems </span><span style="color:#666600">( </span><span style="color:#660066">size_t </span><span style="color:#000000">worker_count </span><span style="color:#666600">) </span><span style="color:#666600">{ </span><span style="color:#880000">// 如果 NumIncompleteWorkItems() 需要考慮</span><span style="color:#880000">本地工作列表,則可以使用 |worker_count|,這比自己進行核算更容易,同時記住</span><span style="color:#880000">,實際項目數量可能被高估,因此</span><span style="color:#880000">// 當沒有可用的工作時,可能會調用 WorkerTask()。</span><span style="color:#000088">return </span><span style="color:#660066">GlobalQueueSize </span><span style="color:#666600">() </span><span style="color:#666600">+ </span><span style="color:#000000">worker_count </span><span style="color:#666600">; </span><span style="color:#666600">}</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">PostJob </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindRepeating </span><span style="color:#666600">(& </span><span style="color:#660066">WorkerTask </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindRepeating </span><span style="color:#666600">(& </span><span style="color:#660066">NumIncompleteWorkItems </span><span style="color:#666600">));</span> 
</span></span>

通過在調用時循環執行盡可能多的工作,工作任務可以避免調度開銷。同時,任務base::JobDelegate::ShouldYield()會定期調用,以便有條件地退出,并讓調度程序優先處理其他工作。例如,這種讓步語義允許用戶可見的作業使用所有核心,但在用戶阻塞任務進入時讓出。

向正在運行的作業添加額外工作

當添加新的工作項并且 API 用戶希望額外的線程并行調用工作任務時,JobHandle/JobDelegate::NotifyConcurrencyIncrease()?必須在最大并發數增加后不久調用。

測試

有關更多詳細信息,請參閱測試發布任務的組件。

要測試使用 或 中的函數的代碼base::SingleThreadTaskRunner::CurrentDefaultHandlebase::SequencedTaskRunner::CurrentDefaultHandle請在測試范圍內base/task/thread_pool.h實例化。如果需要 BrowserThreads,請使用而不是。base::test::TaskEnvironmentcontent::BrowserTaskEnvironmentbase::test::TaskEnvironment

測試可以base::test::TaskEnvironment使用 運行 的消息泵base::RunLoop,可以使其運行直到Quit()(明確或通過RunLoop::QuitClosure()),或運行到RunUntilIdle()準備運行的任務并立即返回。

如果在 TestTimeouts::action_timeout() 之后未明確退出,TaskEnvironment 會將 RunLoop::Run() 配置為 GTEST_FAIL()。這比測試代碼未能觸發 RunLoop 退出導致測試掛起要好得多。可以使用 base::test::ScopedRunLoopTimeout 覆蓋超時時間。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">類</span><span style="color:#660066">MyTest </span><span style="color:#666600">:</span><span style="color:#000088">公共</span><span style="color:#000000">測試</span><span style="color:#666600">::</span><span style="color:#660066">測試</span><span style="color:#666600">{</span><span style="color:#000088">公共</span><span style="color:#666600">:</span><span style="color:#880000">//...</span><span style="color:#000088">受保護的</span><span style="color:#666600">:</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#000000">test </span><span style="color:#666600">:: </span><span style="color:#660066">TaskEnvironment </span><span style="color:#000000">task_environment_ </span><span style="color:#666600">; </span><span style="color:#666600">};</span>    <span style="color:#000000">TEST_F </span><span style="color:#666600">(</span><span style="color:#660066">MyTest </span><span style="color:#666600">,</span><span style="color:#660066">FirstTest </span><span style="color:#666600">)</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">A </span><span style="color:#666600">));</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">B </span><span style="color:#666600">));</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostDelayedTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">C </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TimeDelta </span><span style="color:#666600">:: </span><span style="color:#660066">Max </span><span style="color:#666600">());</span>  <span style="color:#880000">// 這將運行 (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle 隊列,直到它為空。</span><span style="color:#880000">// 延遲的任務只有在執行條件成熟時才會添加到隊列中。</span><span style="color:#880000">// 盡可能優先使用明確的退出條件而不是 RunUntilIdle:</span><span style="color:#880000">// bit.ly/run-until-idle-with-care2.base </span><span style="color:#000000">:: </span><span style="color:#660066">RunLoop </span><span style="color:#666600">(). </span><span style="color:#666600">RunUntilIdle </span><span style="color:#660066">( </span><span style="color:#666600">); </span><span style="color:#880000">// A 和 B 已執行。C 尚未成熟。</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">RunLoop </span><span style="color:#000000">run_loop </span><span style="color:#666600">;</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">D </span><span style="color:#666600">));</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">run_loop.QuitClosure </span><span style="color:#666600">());</span><span style="color:#660066">base </span><span style="color:#000000">:: </span><span style="color:#666600">SingleThreadTaskRunner </span><span style="color:#660066">:: </span><span style="color:#666600">GetCurrentDefault </span><span style="color:#660066">(</span><span style="color:#666600">)-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#666600">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#000000">:: </span><span style="color:#666600">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">E </span><span style="color:#666600">))</span><span style="color:#660066">;</span><span style="color:#880000">// 這將運行 (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle 隊列,直到</span><span style="color:#880000">調用</span><span style="color:#000000">QuitClosure。run_loop.Run </span><span style="color:#666600">(); </span><span style="color:#666600">// D 和 run_loop.QuitClosure() 已執行</span><span style="color:#660066">。E</span><span style="color:#880000">仍在隊列中。</span><span style="color:#880000">// 發布到線程池的任務在發布時異步運行。base </span><span style="color:#666600">:: </span><span style="color:#666600">ThreadPool </span><span style="color:#660066">:: </span><span style="color:#666600">PostTask </span><span style="color:#660066">(</span><span style="color:#666600">FROM_HERE </span><span style="color:#000000">,</span><span style="color:#666600">{ </span><span style="color:#666600">},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">F </span><span style="color:#666600">)); </span><span style="color:#000088">auto </span><span style="color:#000000">task_runner </span><span style="color:#000000">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSequencedTaskRunner </span><span style="color:#666600">({}); </span><span style="color:#000000">task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">G </span><span style="color:#666600">));</span> <span style="color:#880000">// 阻塞直到發布到線程池的所有任務都運行完成:</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPoolInstance </span><span style="color:#666600">:: </span><span style="color:#660066">Get </span><span style="color:#666600">()-> </span><span style="color:#660066">FlushForTesting </span><span style="color:#666600">(); </span><span style="color:#880000">// F 和 G 已經執行。</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTaskAndReplyWithResult </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(&</span><span style="color:#000000">H </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(&</span><span style="color:#000000">I </span><span style="color:#666600">));</span> <span style="color:#880000">// 這將運行 (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle 隊列,直到</span><span style="color:#880000">// (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle 隊列和 ThreadPool 隊列都為</span><span style="color:#880000">// 空。盡可能優先使用明確的退出條件而不是 RunUntilIdle:</span><span style="color:#880000">// bit.ly/run-until-idle-with-care2. </span><span style="color:#000000">task_environment_ </span><span style="color:#666600">. </span><span style="color:#660066">RunUntilIdle </span><span style="color:#666600">(); </span><span style="color:#880000">// E、H、I 已執行。</span><span style="color:#666600">}</span></span></span>

在新進程中使用線程池

在使用ThreadPoolInstance 函數之前,需要在進程中初始化 ThreadPoolInstance。Chromebase/task/thread_pool.h瀏覽器進程及其子進程(渲染器、GPU、實用程序)中的 ThreadPoolInstance 初始化已完成。要在其他進程中使用 ThreadPoolInstance,請在主函數的早期階段初始化 ThreadPoolInstance:

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// 這將使用默認參數初始化并啟動 ThreadPoolInstance。base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPoolInstance </span><span style="color:#666600">:: </span><span style="color:#660066">CreateAndStartWithDefaultParams </span><span style="color:#666600">(</span><span style="color:#008800">“process_name” </span><span style="color:#666600">); // </span><span style="color:#000000">
base </span><span style="color:#880000">/task/thread_pool.h API 現在可以與 base::ThreadPool 特征一起使用。//</span><span style="color:#880000">任務將在發布時進行安排。</span><span style="color:#880000">// 這將初始化 ThreadPoolInstance。base </span><span style="color:#000000">
:: </span><span style="color:#660066">ThreadPoolInstance </span><span style="color:#666600">:: </span><span style="color:#660066">Create </span><span style="color:#666600">( </span><span style="color:#008800">"process_name" </span><span style="color:#666600">); </span><span style="color:#880000">// base/task/thread_pool.h API 現在可與 base::ThreadPool 特性一起使用。在調用 Start() 之前,不會</span><span style="color:#666600">創建</span><span style="color:#880000">任何線程,也不會安排任何任務</span><span style="color:#666600">。base :: </span><span style="color:#880000">ThreadPoolInstance </span><span style="color:#000000">
:: </span><span style="color:#660066">Get </span><span style="color:#666600">( </span><span style="color:#660066">) </span><span style="color:#666600">-> </span><span style="color:#660066">Start </span><span style="color:#666600">( </span><span style="color:#000000">params </span><span style="color:#666600">); </span><span style="color:#880000">// ThreadPool 現在可以創建線程并安排任務。</span></span></span>

并在主函數中關閉ThreadPoolInstance:

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPoolInstance </span><span style="color:#666600">:: </span><span style="color:#660066">Get </span><span style="color:#666600">()-> </span><span style="color:#660066">Shutdown </span><span style="color:#666600">(); </span><span style="color:#880000">// 使用 TaskShutdownBehavior::BLOCK_SHUTDOWN 發出的任務和</span><span style="color:#880000">// 使用 TaskShutdownBehavior::SKIP_ON_SHUTDOWN 發出的任務,</span><span style="color:#880000">在 Shutdown() 調用之前已開始運行,現已完成</span><span style="color:#880000">執行。使用 // TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN 發出的任務</span><span style="color:#880000">可能仍在</span><span style="color:#880000">運行。</span></span></span>

TaskRunner 所有權(鼓勵不依賴注入)

TaskRunner 不應該被傳遞到多個組件。相反,應該由使用 TaskRunner 的組件來創建它。

請參閱此重構示例,其中 TaskRunner 被傳遞到多個組件,最終在葉子節點中使用。現在,葉子節點可以并且應該直接從 獲取其 TaskRunner?base/task/thread_pool.h。

如上所述,base::test::TaskEnvironment允許單元測試控制從底層 TaskRunner 發出的任務。在極少數情況下,測試需要更精確地控制任務順序:TaskRunner 的依賴注入可能會很有用。對于這種情況,首選方法如下:

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">類</span><span style="color:#660066">Foo </span><span style="color:#666600">{</span><span style="color:#000088">公共</span><span style="color:#666600">:</span>  <span style="color:#880000">// 在測試中覆蓋 |background_task_runner_|。void </span><span style="color:#666600">SetBackgroundTaskRunnerForTesting </span><span style="color:#660066">( </span><span style="color:#666600">scoped_refptr </span><span style="color:#000088">< </span><span style="color:#666600">base </span><span style="color:#000000">:: </span><span style="color:#666600">SequencedTaskRunner </span><span style="color:#660066">> </span><span style="color:#000000">background_task_runner </span><span style="color:#000000">) </span><span style="color:#666600">{ </span><span style="color:#666600">background_task_runner_ </span><span style="color:#000000">= </span><span style="color:#666600">std </span><span style="color:#000000">:: </span><span style="color:#666600">move </span><span style="color:#000000">( </span><span style="color:#666600">background_task_runner </span><span style="color:#000000">) </span><span style="color:#666600">; </span><span style="color:#666600">}</span><span style="color:#000088">私有</span><span style="color:#666600">:</span><span style="color:#000000">scoped_refptr </span><span style="color:#666600">< </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">> </span><span style="color:#000000">background_task_runner_ </span><span style="color:#666600">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSequencedTaskRunner </span><span style="color:#666600">(</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">MayBlock </span><span style="color:#666600">(),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">}); </span><span style="color:#666600">}</span></span></span>

請注意,這仍然允許刪除 //chrome 和該組件之間的所有管道層,因為單元測試將直接使用葉層。

常問問題

有關更多示例,請參閱線程和任務常見問題解答。

內部

序列管理器

SequenceManager管理具有不同屬性(例如優先級、通用任務類型)的任務隊列 (TaskQueue),將所有已發布的任務多路復用到單個后臺序列中。這通常是一個 MessagePump。根據所使用的消息泵類型,其他事件(例如 UI 消息)也可能會被處理。在 Windows 上,APC 調用(如果時間允許)以及發送到已注冊的 HANDLE 集合的信號也可能會被處理。

消息泵

MessagePump負責處理原生消息,并定期將周期分配給其委托(SequenceManager)。MessagePump 會將委托回調與原生消息處理混合使用,確保兩種類型的事件都不會導致對方的周期不足。

有不同的MessagePumpTypes,最常見的是:

  • 默認:僅支持任務和計時器

  • UI:支持本機UI事件(例如Windows消息)

  • IO:支持異步IO(不是文件I/O!)

  • 自定義:用戶提供的 MessagePump 接口實現

運行循環

RunLoop 是一個輔助類,用于運行與當前線程(通常是 SequenceManager)關聯的 RunLoop::Delegate。在堆棧上創建一個 RunLoop,然后調用 Run/Quit 來運行嵌套的 RunLoop,但請避免在生產代碼中使用嵌套循環!

任務重入

SequenceManager 具有任務重入保護功能。這意味著,如果正在處理一個任務,則第二個任務在第一個任務完成之前無法啟動。重入可以在處理任務時發生,并會創建一個內部消息泵。該內部消息泵隨后處理可以隱式啟動內部任務的原生消息。內部消息泵可以通過對話框 (DialogBox)、通用對話框 (GetOpenFileName)、OLE 函數 (DoDragDrop)、打印機函數 (StartDoc) 以及許多其他函數創建。

<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">需要內部任務處理時的</span><span style="color:#660066">示例解決方法</span><span style="color:#666600">:</span><span style="color:#000000">HRESULT hr </span><span style="color:#666600">; </span><span style="color:#666600">{ </span><span style="color:#660066">CurrentThread </span><span style="color:#666600">:: </span><span style="color:#660066">ScopedAllowApplicationTasksInNativeNestedLoop </span><span style="color:#000000">allow </span><span style="color:#666600">; </span><span style="color:#000000">hr </span><span style="color:#666600">= </span><span style="color:#660066">DoDragDrop </span><span style="color:#666600">(...); </span><span style="color:#880000">//隱式運行模態消息循環。</span><span style="color:#666600">} </span><span style="color:#880000">//處理|hr|(DoDragDrop()返回的結果)。</span></span></span>

在使用 CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop 之前,請確保您的任務是可重入的(可嵌套的),并且所有全局變量都是穩定且可訪問的。

通用 API

用戶代碼幾乎不需要直接訪問 SequenceManager API,因為它們是為處理調度的代碼而設計的。您應該使用以下代碼:

  • base::RunLoop:從綁定的線程驅動 SequenceManager。

  • base::Thread/SequencedTaskRunner::CurrentDefaultHandle:從正在運行的任務發回 SequenceManager TaskQueues。

  • SequenceLocalStorageSlot :將外部狀態綁定到序列。

  • base::CurrentThread :與當前線程綁定的任務相關 API 子集的代理

  • 嵌入器可以提供自己的靜態訪問器來在特定循環上發布任務(例如 content::BrowserThreads)。

SingleThreadTaskExecutor 和 TaskEnvironment

無需處理需要簡單任務發布環境(一個默認任務隊列)的 SequenceManager 和 TaskQueues 代碼,而是可以使用SingleThreadTaskExecutor。

單元測試可以使用高度可配置的TaskEnvironment 。

MessageLoop 和 MessageLoopCurrent

您可能會在代碼或文檔中看到對 MessageLoop 或 MessageLoopCurrent 的引用。這些類已不再存在,我們正在處理或刪除對它們的所有引用。base::MessageLoopCurrent已被替換base::CurrentThread,并且已刪除base::MessageLooparebase::SingleThreadTaskExecutor和 的替換項base::Test::TaskEnvironment

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/82491.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/82491.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/82491.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

詳解osgb的頂點,紋理,索引,UV讀取與存儲

virtual void apply(osg::Geode& node) {for (int i 0; i < node.getNumDrawables(); i){osg::Geometry* geometry dynamic_cast<osg::Geometry*>(node.getDrawable(i));if (geometry){//apply(*g);//***********************************************//解析頂點…

CSS闖關指南:從手寫地獄到“類”積木之旅|得物技術

一、背景 在Web開發網頁設計中&#xff0c;CSS&#xff08;層疊樣式表&#xff09;扮演著至關重要的角色&#xff0c;它用于控制網頁的布局、外觀和視覺效果。CSS不僅可以美化網頁的視覺表現&#xff0c;還可以提高網頁的可訪問性、可維護性和響應式設計。在我們進行網頁開發的…

【大模型應用開發】Qwen2.5-VL-3B識別視頻

0. 編寫代碼并嘗試運行 克隆以下代碼 git clone https://gitee.com/ai-trailblazer/qwen-vl-hello.git 嘗試運行qwen-vl-hello.py&#xff0c;報錯原因缺少modelscope&#xff1a; 1. 安裝qwen-vl-utils工具包 pip install qwen-vl-utils[decord]0.0.8 嘗試運行&#xff0c;…

MySQL 窗口函數深度解析:語法、應用場景與性能優化

一、窗口函數核心概念 ??本質??&#xff1a;對一組與當前行相關聯的行執行計算&#xff0c;??不改變原表行數?? ??與聚合函數的區別??&#xff1a; SELECT department, AVG(salary) -- 普通聚合&#xff1a;每個部門一行 FROM employees GROUP BY department;SE…

新版Chrome瀏覽器加載eDrawings 3D Viewer控件網頁查看DWG、DXF

eDrawings是一款由達索系統&#xff08;DASSAULT SYSTMES&#xff09;開發的免費跨平臺CAD看圖工具&#xff0c;專注于3D模型和2D工程圖的查看、協作與共享。其核心功能包括多格式支持、動態模型展示、跨平臺適配及輕量化操作體驗&#xff0c;適用于工程設計、教育培訓等領域。…

阿姆斯特朗數

阿姆斯特朗數也就是俗稱的水仙花數&#xff0c;是指一個n位數&#xff0c;其各位數字的n次方之和等于該數本身。例如&#xff0c;153是一個水仙花數&#xff0c;因為153&#xff1d;13&#xff0b;53&#xff0b;33。請問100-10000所有水仙花數有哪些。 采用窮舉法對范圍之間的…

vmvare 虛擬機內存不足

centos 擴展物理卷df -hT / sudo du -hx --max-depth1 / | sort -rh | head -n 20 // 查看前20個的大文件 # 清理舊日志&#xff08;保留最近7天&#xff09; sudo find /var/log -type f -mtime 7 -delete sudo journalctl --vacuum-time7d # 清理yum緩存 sudo yum clean …

C++?繼承!!!

一、引言 代碼的復用對于代碼的質量以及程序員的代碼設計上都是非常重要的&#xff0c;C中的許多特性都體現了這一點&#xff0c;從函數復用、模板的引入到今天我們將一起學習的&#xff1a;繼承 二、什么是繼承&#xff1f; 1、繼承的概念 繼承(inheritance)機制是面向對象程…

Android設置界面層級為最上層實現

Android設置界面層級為最上層實現 文章目錄 Android設置界面層級為最上層實現一、前言二、Android設置界面層級為最上層實現1、主要代碼2、后遺癥 三、其他1、Android設置界面層級為最上層小結2、懸浮框的主要代碼懸浮框 注意事項&#xff08;1&#xff09;權限限制&#xff08…

Linux 了解篇

一、GNU 項目與 GPL 許可 &#xff08;一&#xff09;GNU 項目 GNU &#xff1a;GNU 是一個遞歸縮寫&#xff0c;代表 "GNUs Not Unix"。GNU 項目旨在開發一個完全自由的操作系統&#xff0c;該操作系統基于 Unix 的設計理念但不包含 Unix 的代碼。GNU 項目提供了大…

word 如何讓公式居中,公式編號右對齊

問題&#xff1a; 如何讓輸入的公式居中&#xff0c;公式編號右對齊&#xff1f; 解決方法&#xff1a; 方法一&#xff1a;使用制表符 1、輸入內容&#xff1a;先按一次“Tab”鍵&#xff08;制表符&#xff09;&#xff0c;然后鍵入公式&#xff0c;然后再按一次“Tab”鍵…

華為OD機試真題——最小循環子數組 (2025B卷:100分)Java/python/JavaScript/C/C++/GO最佳實現

2025 B卷 100分 題型 本專欄內全部題目均提供Java、python、JavaScript、C、C++、GO六種語言的最佳實現方式; 并且每種語言均涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、3個測試用例以及綜合分析; 本文收錄于專欄:《2025華為OD真題目錄+全流程解析+備考攻略+經驗分…

OpenCv高階(十七)——dlib庫安裝、dlib人臉檢測

文章目錄 前言一、dlib庫簡介二、dlib庫安裝1、本地安裝&#xff08;離線&#xff09;2、線上安裝 三、dlib人臉檢測原理1、HOG 特征提取2、 SVM 分類器訓練3、 滑動窗口搜索4、非極大值抑制&#xff08;NMS&#xff09; 四、dlib人臉檢測代碼1、導入OpenCV計算機視覺庫和dlib機…

AD-PCB--AD20軟件安裝及中英文切換 DAY 2

1.軟件安裝 1.1 軟件包下載 給你一個捷徑&#xff1a; 1.2 安裝過程&#xff08;安裝過的人跳過就好&#xff0c;一般很多都支持懶人安裝&#xff09; 雙擊其中的exe文件 點擊下一步 選擇中文 接受用戶協議 下面這個彈窗有的沒有。 建議勾選導入導出 安裝目錄&#xff0c…

單向循環鏈表與雙向鏈表

單向循環鏈表的原理與應用 思考&#xff1a;對于單向鏈表而言&#xff0c;想要遍歷鏈表&#xff0c;則必須從鏈表的首結點開始進行遍歷&#xff0c;請問有沒有更簡單的方案實現鏈表中的數據的增刪改查&#xff1f; 回答&#xff1a;是有的&#xff0c;可以使用單向循環的鏈表進…

Windows鼠標掉幀測試與修復

前言 這兩天突然發現鼠標似乎有掉幀&#xff0c;但是掉的又不太明顯&#xff0c;用著感覺似乎快速移動的時候會有一瞬間卡一下&#xff0c;但是眼睛又看不清楚&#xff0c;不太確定是不是自己的心理作用&#xff0c;非常難受。 如何判斷鼠標是否掉幀 根據我的經驗&#xff0…

U 盤數據恢復全攻略

目錄 &#x1f4be; U盤數據誤刪怎么辦&#xff1f;兩款實用工具助你找回丟失文件&#xff01;1?? Recover My Files&#xff1a;數據恢復的得力助手&#x1f4cc; 主要特點&#x1f6e0; 使用步驟詳解1. 下載與安裝2. 啟動軟件并選擇恢復類型3. 選擇U盤所在分區4. 選擇文件恢…

HarmonyOS NEXT~鴻蒙系統運維:全面解析與最佳實踐

HarmonyOS NEXT&#xff5e;鴻蒙系統運維&#xff1a;全面解析與最佳實踐 摘要 本文深入探討鴻蒙(HarmonyOS)系統的運維管理&#xff0c;從架構特點到日常維護操作&#xff0c;全面分析這一全場景分布式操作系統的運維要點。文章將介紹鴻蒙系統特有的分布式能力運維管理、性能…

基于 STM32 的智慧農業溫室控制系統設計與實現

摘要 本文提出一種基于 STM32 微控制器的智慧農業溫室控制系統設計方案,通過集成多類型環境傳感器、執行機構及無線通信模塊,實現對溫室內溫濕度、光照、土壤濕度等參數的實時監測與自動調控。文中詳細闡述硬件選型、電路連接及軟件實現流程,并附關鍵代碼示例,為智慧農業領…

Appium+python自動化(五)- 模擬器

簡介 Appium是做安卓自動化的一個比較流行的工具&#xff0c;對于想要學習該工具但是又局限于沒 android 手機來說&#xff0c;可以通過安卓模擬器來解決該問題&#xff0c;下面就講解使用appium連接安卓模擬器的操作步驟。而是由于手機數據線問題&#xff0c;也只好先用模擬器…