先附上可用于学习的开源代码:Base库
喜欢可以帮忙Star一下
编译:参考Base库即可
环境:Visual Studio 2022 - 17.8.3 + v143 + 10.0.22621.0 + C++17
Base 库中的 RunLoop 是一个事件循环机制,用于处理异步事件和任务调度。它提供了一种方便的方式来管理和调度事件的处理,特别适用于多线程和异步编程环境。
RunLoop 的主要特点和功能包括:
Chromium 使用 RunLoop 来管理和调度各种异步操作,如网络请求、定时器、UI 事件等。它是 Chromium 内部的核心机制之一,为 Chromium 浏览器的高性能和稳定性做出了重要贡献。
创建基本的事件循环,可以让一个线程从干一件事就退出,变为可以循环干很多件事。从这个角度出发,结合base::SimpleThread可以使之成为base::Thread,相当于增强了base::SimpleThread。
RunLoop与SimpleThread相同,将真正的Run函数代理给其中的Delegate类,这种代理设计模式在Chromium大量存在
RunLoop::Delegate 是一个通用接口,允许 RunLoop 与该线程的消息循环的底层实现分离。
它持有在关联线程上由 RunLoop 使用的私有状态。
在使用 RunLoop 实例和 RunLoop 静态方法之前,必须通过 RunLoop::RegisterDelegateForCurrentThread() 在给定线程上注册一个且仅一个 RunLoop::Delegate。完整源码如下:
class BASE_EXPORT Delegate {
public:
Delegate();
virtual ~Delegate();
virtual void Run(bool application_tasks_allowed, TimeDelta timeout) = 0;
virtual void Quit() = 0;
// 当 RunLoop 是类型 kNestableTasksAllowed 时,在 RunLoop 进入此 Delegate 的嵌套 Run() 调用之前调用。
// Delegate 应确保即将发生的 Run() 调用将处理排在其前面的应用程序任务,而无需进一步探测。
// 例如,在某些平台上,如 Mac,消息泵需要显式请求在嵌套时处理应用程序任务,否则它们只会等待系统消息。
virtual void EnsureWorkScheduled() = 0;
protected:
// 返回此 Delegate 的 |should_quit_when_idle_callback_| 的结果。
// 仅由 Delegate 本身调用,因此设置为 "protected"。
bool ShouldQuitWhenIdle();
private:
// 虽然状态由 Delegate 子类持有,但只有 RunLoop 可以使用它。
friend class RunLoop;
// 使用基于向量的堆栈比默认的双端队列堆栈更节省内存,因为预计活动的 RunLoop 堆栈不会超过几个条目。
using RunLoopStack = stack<RunLoop*, std::vector<RunLoop*>>;
RunLoopStack active_run_loops_;
ObserverList<RunLoop::NestingObserver>::Unchecked nesting_observers_;
#if DCHECK_IS_ON()
bool allow_running_for_testing_ = true;
#endif
// 一旦通过 RegisterDelegateForCurrentThread() 将此 Delegate 绑定到线程,此值将为 true。
bool bound_ = false;
// Thread-affine per its use of TLS.
THREAD_CHECKER(bound_thread_checker_);
DISALLOW_COPY_AND_ASSIGN(Delegate);
};
这两个函数一般用于RunLoop来调用,通知其注册的Delegate来运行/终止。实现应该从 Run() 调用开始同步运行,直到收到匹配的 Quit() 调用或超时的 |timeout|。收到 Quit() 调用或超时后,应尽快从 Run() 调用返回,而不执行剩余的任务/消息。
Run() 调用可以嵌套,在这种情况下,每个 Quit() 调用应导致最顶层的活动 Run() 调用返回。Run() 返回的另一个触发条件是 Delegate 在空闲时应该先探测 |shouldquit_when_idle_callback|,然后再休眠。
如果此栈上是首次 Run() 调用,或者是从 Type::kNestableTasksAllowed 的嵌套 RunLoop 调用的话,|application_tasks_allowed| 为 true(否则此 Run() 级别应仅处理系统任务)。
注意Run和Quit都是纯虚函数,意味着不能直接使用Deltegate类,必须继承使用
enum class Type {
kDefault,
kNestableTasksAllowed,
};
RunLoop 的类型:顶级(非嵌套)的 RunLoop(kDefault)将处理分配给其 Delegate 的系统和应用程序任务。然而,当嵌套时,RunLoop(kDefault) 仅处理系统任务,而 RunLoop(kNestableTasksAllowed) 将继续处理应用程序任务。
这在递归 RunLoop 的情况下非常重要。在使用常见控件或打印机功能时,可能会出现一些不需要的运行循环。默认情况下,禁用递归任务处理。
一般来说,应尽量避免使用可嵌套的 RunLoop。它们很危险,很难正确使用,请谨慎使用。
一个具体的例子是:
默认构造type = Default,表明非可嵌套,尽量避免创建嵌套循环
RunLoop::RunLoop(Type type)
: delegate_(GetTlsDelegate().Get()),
type_(type),
origin_task_runner_(ThreadTaskRunnerHandle::Get()) {
DCHECK(delegate_) << "A RunLoop::Delegate must be bound to this thread prior "
"to using RunLoop.";
DCHECK(origin_task_runner_);
}
RunLoop::~RunLoop() {
// TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235.
// DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
这里可以看到,RunLoop执行任务的能力是由ThreadTaskRunnerHandle::Get()赋予的
源码注释如下:
// 运行当前的 RunLoop::Delegate。这会阻塞直到调用 Quit。在调用 Run 之前,请确保获取 QuitClosure 以便异步停止 RunLoop::Delegate。
void Run();
// 运行当前的 RunLoop::Delegate。这会阻塞直到经过 |timeout| 或调用 Quit。支持嵌套多个具有和不具有超时的 runloop。如果内部循环的超时时间比外部循环长,当内部循环退出时,外部循环将立即退出。
void RunWithTimeout(TimeDelta timeout);
// 运行当前的 RunLoop::Delegate,直到其队列中不再有任何任务或消息(即进入空闲状态)。
// 警告1:这可能运行时间很长(可能会超时),甚至永远不会返回!当存在重复任务(例如动画网页)时,请勿使用此方法。
// 警告2:这可能会过早返回!例如,如果用于运行直到发生传入事件,但该事件依赖于不同队列中的任务(例如另一个 TaskRunner 或系统事件)。
// 根据上述警告,这往往会导致测试不稳定;在可能的情况下,更倾向于使用
QuitClosure()+Run()。
void RunUntilIdle();
// 返回当前是否正在运行
bool running() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return running_;
}
// Quit() 立即终止先前对 Run() 的调用。QuitWhenIdle() 在队列中没有任务或消息时终止先前对 Run() 的调用。
//
// 这些方法是线程安全的,但请注意,当从另一个线程调用 Quit() 时,它是异步的(会很快退出,但已经排队的任务会先执行)。
//
// 可能会有其他嵌套的 RunLoop 服务于相同的任务队列。终止一个 RunLoop 对其他 RunLoop 没有影响。Quit() 和 QuitWhenIdle() 可以在 Run() 之前、期间或之后调用。如果在调用 Run() 之前调用 Quit() 或 QuitWhenIdle(),则调用 Run() 将立即返回。在 RunLoop 已经完成运行后调用 Quit() 或 QuitWhenIdle() 没有效果。
//
// 警告:您绝不能假设调用 Quit() 或 QuitWhenIdle() 将终止目标消息循环。如果嵌套的 RunLoop 继续运行,目标可能永远不会终止。在这种情况下,很容易发生活锁(永远运行)。
void Quit();
void QuitWhenIdle();
// 返回一个 RepeatingClosure,安全地调用 Quit() 或 QuitWhenIdle()(如果 RunLoop 实例不存在,则没有效果)。
//
// 这些方法必须从创建 RunLoop 的线程调用。
//
// 返回的闭包可以安全地:
// * 传递到其他线程。
// * 从其他线程运行 Run(),尽管这将异步地终止 RunLoop。
// * 在 RunLoop 停止或被销毁后运行,此时它们不起作用。
// * 在 RunLoop::Run() 之前运行,此时 RunLoop::Run() 将立即返回。
//
// 例子:
// RunLoop run_loop;
// DoFooAsyncAndNotify(run_loop.QuitClosure());
// run_loop.Run();
// 请注意,Quit() 本身是线程安全的,如果您可以从另一个线程访问 RunLoop 引用(例如从捕获的 lambda 函数或测试观察者中),可以直接调用它。
RepeatingClosure QuitClosure();
RepeatingClosure QuitWhenIdleClosure();
// 如果当前线程上有一个活动的 RunLoop,则返回 true。
// 在调用 RegisterDelegateForCurrentThread() 之前调用是安全的。
static bool IsRunningOnCurrentThread();
// 如果当前线程上有一个活动的 RunLoop,并且它嵌套在另一个活动的 RunLoop 中,则返回 true。
// 在调用 RegisterDelegateForCurrentThread() 之前调用是安全的。
static bool IsNestedOnCurrentThread();
// 针对当前线程管理嵌套的观察者,这样可以在RunLoop运行前和运行后都能收到通知,仅针对存在嵌套的RunLoop
static void AddNestingObserverOnCurrentThread(NestingObserver* observer);
static void RemoveNestingObserverOnCurrentThread(NestingObserver* observer);
// 在当前线程上注册 |delegate|。在使用 RunLoop 方法之前,必须在每个线程上调用一次且仅一次。从那时起,|delegate| 将永远与该线程绑定(包括其销毁)。
static void RegisterDelegateForCurrentThread(Delegate* delegate);
// 终止活动的 RunLoop(在空闲时)-- 必须存在一个。这些方法被引入作为长期弃用的 MessageLoop::Quit(WhenIdle)(Closure) 方法的首选临时替代方法。调用者应该正确地传递对应的 RunLoop 实例(或其 QuitClosure)的引用,而不是使用这些方法,以将 Run()/Quit() 链接到单个 RunLoop 实例并提高可读性。
static void QuitCurrentDeprecated();
static void QuitCurrentWhenIdleDeprecated();
static RepeatingClosure QuitCurrentWhenIdleClosureDeprecated();
// RunLoop::Delegate 的缓存引用,用于驱动此 RunLoop 的线程,以便快速访问而无需使用 TLS(还允许在 Run() 过程中从另一个序列访问状态,参考下面的 |sequence_checker_|)。
Delegate* const delegate_;
const Type type_;
#if DCHECK_IS_ON()
bool run_called_ = false;
#endif
bool quit_called_ = false;
bool running_ = false;
// 用于记录在此 RunLoop 上调用了 QuitWhenIdle(),意味着 Delegate 应该在变为空闲时退出 Run()(它负责通过 ShouldQuitWhenIdle() 探测此状态)。此状态存储在这里而不是推送到 Delegate,以支持嵌套的 RunLoops。
bool quit_when_idle_received_ = false;
// 如果允许使用 QuitCurrentDeprecated(),则为 true。从 RunLoop 中获取 QuitClosure() 会隐式将其设置为 false,因此在该 RunLoop 运行时不能使用 QuitCurrent*Deprecated()。
bool allow_quit_current_deprecated_ = true;
// RunLoop 不是线程安全的。除非标记为线程安全,否则其状态/方法不得从构造它的线程以外的任何序列访问。例外情况:在 Run() 过程中可以安全地从另一个序列(或单个并行任务)访问 RunLoop -- 例如,在测试中不必在整个测试中传递 ThreatTaskRunnerHandle::Get() 来重新发布 QuitClosure 到原始线程。
SEQUENCE_CHECKER(sequence_checker_);
const scoped_refptr<SingleThreadTaskRunner> origin_task_runner_;
// QuitClosure 的 WeakPtrFactory,用于安全性。
WeakPtrFactory<RunLoop> weak_factory_{ this };
DISALLOW_COPY_AND_ASSIGN(RunLoop);
bool RunLoop::BeforeRun() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
DCHECK(delegate_->allow_running_for_testing_)
<< "RunLoop::Run() isn't allowed in the scope of a "
"ScopedDisallowRunningForTesting. Hint: if mixing "
"TestMockTimeTaskRunners on same thread, use TestMockTimeTaskRunner's "
"API instead of RunLoop to drive individual task runners.";
DCHECK(!run_called_);
run_called_ = true;
#endif // DCHECK_IS_ON()
// Allow Quit to be called before Run.
if (quit_called_)
return false;
auto& active_run_loops = delegate_->active_run_loops_;
active_run_loops.push(this);
const bool is_nested = active_run_loops.size() > 1;
if (is_nested) {
for (auto& observer : delegate_->nesting_observers_)
observer.OnBeginNestedRunLoop();
if (type_ == Type::kNestableTasksAllowed)
delegate_->EnsureWorkScheduled();
}
running_ = true;
return true;
}
void RunLoop::AfterRun() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
running_ = false;
auto& active_run_loops = delegate_->active_run_loops_;
DCHECK_EQ(active_run_loops.top(), this);
active_run_loops.pop();
// Exiting a nested RunLoop?
if (!active_run_loops.empty()) {
for (auto& observer : delegate_->nesting_observers_)
observer.OnExitNestedRunLoop();
// Execute deferred Quit, if any:
if (active_run_loops.top()->quit_called_)
delegate_->Quit();
}
}
实际调用的RunWithTimeout(TimeDelta::Max());
void RunLoop::Run() {
RunWithTimeout(TimeDelta::Max());
}
void RunLoop::RunWithTimeout(TimeDelta timeout) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!BeforeRun())
return;
// If there is a ScopedRunTimeoutForTest active then set the timeout.
// TODO(crbug.com/905412): Use real-time for Run() timeouts so that they
// can be applied even in tests which mock TimeTicks::Now().
CancelableOnceClosure cancelable_timeout;
ScopedRunTimeoutForTest* run_timeout = ScopedRunTimeoutForTestTLS().Get();
if (run_timeout) {
cancelable_timeout.Reset(
BindOnce(&OnRunTimeout, Unretained(this), run_timeout->on_timeout()));
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, cancelable_timeout.callback(), run_timeout->timeout());
}
DCHECK_EQ(this, delegate_->active_run_loops_.top());
const bool application_tasks_allowed =
delegate_->active_run_loops_.size() == 1U ||
type_ == Type::kNestableTasksAllowed;
delegate_->Run(application_tasks_allowed, timeout);
AfterRun();
}
RunLoop使用必须要继承RunLoop::Delegate来使用,又由于RunLoop需要绑定线程来进行事件循环,所以借助SimpleThread来创建事件循环
SimpleThread用法可以参考SimpleThread
// 定义RunLoop必须的delegate
class MyRunLoopDelegate : public base::RunLoop::Delegate {
public:
explicit MyRunLoopDelegate() {}
~MyRunLoopDelegate() override = default;
void Run(bool application_tasks_allowed, base::TimeDelta timeout) override {
L_TRACE(L"%s", __FUNCTIONW__);
};
void Quit() override {
L_TRACE(L"%s", __FUNCTIONW__);
};
void EnsureWorkScheduled() override {
L_TRACE(L"%s", __FUNCTIONW__);
};
};
// 定义SampleThread的基本运行逻辑
class SimpleDelegate : public base::DelegateSimpleThread::Delegate {
// 基本结构
public:
explicit SimpleDelegate() {}
~SimpleDelegate() override = default;
private:
// 覆写继承的Run
void Run() override {
L_TRACE(L"%s", __FUNCTIONW__);
// 做一个线程逻辑
// 此时线程还没运行,我们创建RunLoop
MyRunLoopDelegate runloop_delegate;
base::RunLoop::RegisterDelegateForCurrentThread(&runloop_delegate);
// 等注册了delegate函数后,可以创建run_loop了
// 创建了RunLoop后可以
base::RunLoop run_loop(base::RunLoop::Type::kDefault);
run_loop.Run();
L_TRACE(L"%s begin run loop", __FUNCTIONW__);
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5));
L_TRACE(L"%s after sleep", __FUNCTIONW__);
run_loop.Quit();
L_TRACE(L"%s after quit = [%d]", __FUNCTIONW__, run_loop.running());
}
};
int main() {
// 创建SimpleThread
SimpleDelegate delegate;
base::DelegateSimpleThread thread(&delegate, "test run loop");
thread.Start();
L_TRACE(L"%s start", __FUNCTIONW__);
thread.Join();
L_TRACE(L"%s has join", __FUNCTIONW__);
}
运行报错
前文有说过,RunLoop执行任务能力是由创建时所处线程的TaskRunner来提供,所以SimpleThread是不具有TaskRunner的,才会报错,需要改用base::Thread
base::Thread可以参考:base::Thread
base::Thread自带RunLoop,所以再运行一个RunLoop则会出现嵌套RunLoop导致base::Thread永远无法退出,需要在第二个RunLoop这里做严格的管控
源码如下:
// 定义RunLoop必须的delegate
class MyRunLoopDelegate : public base::RunLoop::Delegate {
public:
explicit MyRunLoopDelegate() {}
~MyRunLoopDelegate() override = default;
void Run(bool application_tasks_allowed, base::TimeDelta timeout) override {
L_TRACE(L"%s", __FUNCTIONW__);
};
void Quit() override {
L_TRACE(L"%s", __FUNCTIONW__);
};
void EnsureWorkScheduled() override {
L_TRACE(L"%s", __FUNCTIONW__);
};
};
void RegisterRunLoop() {
// 在线程中创建RunLoop
// 这里base::Thread已经有了delegate,不需要额外创建自定义的
// MyRunLoopDelegate runloop_delegate;
// base::RunLoop::RegisterDelegateForCurrentThread(&runloop_delegate);
// 等注册了delegate函数后,可以创建run_loop了,如果是base::Thread,则不需要注册
base::RunLoop run_loop(base::RunLoop::Type::kDefault);
// 3秒后退出事件循环
// base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::BindOnce(&base::RunLoop::Quit, base::Unretained(&run_loop)), base::TimeDelta::FromSeconds(3));
// 这里的延时任务会因为下面的run_loop执行hung住当前线程导致无法执行,所以换成RunWithTimeout,或者另起线程来做退出的操作
run_loop.RunWithTimeout(base::TimeDelta::FromSeconds(3)); // 这里会hung住当前线程,3秒自动退出事件循环
L_TRACE(L"%s begin run loop", __FUNCTIONW__);
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5));
L_TRACE(L"%s after sleep", __FUNCTIONW__);
run_loop.Quit();
L_TRACE(L"%s after quit = [%d]", __FUNCTIONW__, run_loop.running());
}
int main() {
// 创建SimpleThread
base::Thread thread("run loop test");
thread.Start();
// 线程创建RunLoop
thread.task_runner()->PostTask(FROM_HERE, base::BindOnce(&RegisterRunLoop));
// 等待所有任务完成
thread.FlushForTesting();
// 停止线程
thread.Stop();
}
总结:一般情况RunLoop可以不用,除非有特殊的场景需要,主要是用来了解Chromium底层的多线程设计。
谢谢各位看到这里,如果有感兴趣的模块或者代码需要攻略,也可以留言,会不定时更新。喜欢可以去github点点赞,再次感谢🙏
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。