CEF线程之multi_threaded_message_loop参数

2023-12-28 16:36

本文主要是介绍CEF线程之multi_threaded_message_loop参数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • JS调用C++方法,OnQuery消息传递线程过程详解
  • CefSettings.multi_threaded_message_loop参数
    • multi_threaded_message_loop
    • external_message_pump
    • MainMessageLoopStd
    • 实验

JS调用C++方法,OnQuery消息传递线程过程详解

之前的文章已经提到过JS调用C++方法的方式,我在开发的过程中碰到一个问题需要验证:
从JS发起的过程,到C++,也就是Browser进程这边,是不是都是同一个线程处理?

首先先琢磨了一下这个消息传递过程:
SimpleHandler::OnProcessMessageReceived
调用
CefMessageRouterBrowserSide的OnProcessMessageReceived:

bool SimpleHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefProcessId source_process,CefRefPtr<CefProcessMessage> message) {CEF_REQUIRE_UI_THREAD();std::cout << "my_browser" << std::endl;if (message_router_->OnProcessMessageReceived(browser, frame, source_process,message)) {return true;}
}

接下来就是看一下OnProcessMessageReceived这个函数干了啥:
在CEF LIB代码的cef_message_router.cc文件中,这个文件定义了CefMessageRouterBrowserSide和CefMessageRouterRenderSide两个类:
基本差不多,我代码里是先用到了CefMessageRouterBrowserSide。

bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefProcessId source_process,CefRefPtr<CefProcessMessage> message) override {CEF_REQUIRE_UI_THREAD();const std::string& message_name = message->GetName();if (message_name == query_message_name_) {cmru::RendererMessage content = cmru::ParseRendererMessage(message);const int context_id = content.context_id;const int request_id = content.request_id;const bool persistent = content.is_persistent;if (handler_set_.empty()) {// No handlers so cancel the query.CancelUnhandledQuery(browser, frame, context_id, request_id);return true;}const int browser_id = browser->GetIdentifier();const int64_t query_id = query_id_generator_.GetNextId();CefRefPtr<CallbackImpl> callback =new CallbackImpl(this, browser_id, query_id, persistent,config_.message_size_threshold, query_message_name_);// Make a copy of the handler list in case the user adds or removes a// handler while we're iterating.const HandlerSet handlers = handler_set_;Handler* handler = std::visit([&](const auto& arg) -> CefMessageRouterBrowserSide::Handler* {for (auto handler : handlers) {bool handled = handler->OnQuery(browser, frame, query_id, arg,persistent, callback.get());if (handled) {return handler;}}return nullptr;},content.payload);// If the query isn't handled nothing should be keeping a reference to// the callback.DCHECK(handler != nullptr || callback->HasOneRef());if (handler) {// Persist the query information until the callback executes.// It's safe to do this here because the callback will execute// asynchronously.QueryInfo* info =new QueryInfo{browser,    frame,    context_id, request_id,persistent, callback, handler};browser_query_info_map_.Add(browser_id, query_id, info);} else {// Invalidate the callback.callback->Detach();// No one chose to handle the query so cancel it.CancelUnhandledQuery(browser, frame, context_id, request_id);}return true;} else if (message_name == cancel_message_name_) {CefRefPtr<CefListValue> args = message->GetArgumentList();DCHECK_EQ(args->GetSize(), 2U);const int browser_id = browser->GetIdentifier();const int context_id = args->GetInt(0);const int request_id = args->GetInt(1);CancelPendingRequest(browser_id, context_id, request_id);return true;}return false;}

在消息传递那一篇说到的两个默认方法名OnQuery的就在这里设置:

  explicit CefMessageRouterBrowserSideImpl(const CefMessageRouterConfig& config): config_(config),query_message_name_(config.js_query_function.ToString() +kMessageSuffix),cancel_message_name_(config.js_cancel_function.ToString() +kMessageSuffix) {}

在上面的代码中,OnProcessMessageReceived主要就是调用了Handle的Onquery方法。
而这个Handle类的定义为(在cef_message_route.h文件中):

  class Handler {public:using Callback = CefMessageRouterBrowserSide::Callback;////// Executed when a new query is received. |query_id| uniquely identifies/// the query for the life span of the router. Return true to handle the/// query or false to propagate the query to other registered handlers, if/// any. If no handlers return true from this method then the query will be/// automatically canceled with an error code of -1 delivered to the/// JavaScript onFailure callback. If this method returns true then a/// Callback method must be executed either in this method or asynchronously/// to complete the query.///virtual bool OnQuery(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int64_t query_id,const CefString& request,bool persistent,CefRefPtr<Callback> callback) {return false;}

所以,我们的MessageHandle类就是继承了这个Handle接口(之前的文章提到过):

class MessageHandler : public CefMessageRouterBrowserSide::Handler{...}bool MessageHandler::OnQuery(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int64 query_id,const CefString& request,bool persistent,CefRefPtr<Callback> callback)
{// record framecurrentFrame = frame;// Only handle messages from the test URL.const std::string& url = frame->GetURL();const char kTestMessageName[] = "demoTest";const std::string& message_name = request;if (message_name == kTestMessageName){dosomething();}
}

这样就实现了JS消息的调用。

也就是说第一个问题:
从JS发起的消息,到render进程,再到Browser进程,再到MessageHandle处理,都是一个串行的过程。而且JS多次发起,也都是同一个线程执行。

第二个问题就出来了,因为我执行程序的时候,multi_threaded_message_loop设置的是false,
。如果multi_threaded_message_loop设置为true,是不是可以实现多线程?

CefSettings.multi_threaded_message_loop参数

在CEF的wiki上有这么一段来描述multi_threaded_message_loop参数。
TID_UI thread is the main thread in the browser process. This thread will be the same as the main application thread if CefInitialize() is called with a CefSettings.multi_threaded_message_loop value of false.

Set CefSettings.multi_threaded_message_loop = true (Windows and Linux only). This will cause CEF to run the browser UI thread on a separate thread from the main application thread. With this approach neither CefDoMessageLoopWork() nor CefRunMessageLoop() need to be called. CefInitialize() and CefShutdown() should still be called on the main application thread. You will need to provide your own mechanism for communicating with the main application thread (see for example the message window usage in cefclient_win.cpp). You can test this mode in cefclient on Windows or Linux by running with the “–multi-threaded-message-loop” command-line flag.

这一段的意思就是说multi_threaded_message_loop为false的时候,就不需要创建一个专门的UI线程来作为主线程,当前进程的主线程就可以作为UI线程来使用。

这里有一个关键点在于,进程自己创建的主线程是参与了windows程序自己的消息循环的,由操作系统来传递和维护这个消息循环。

如果multi_threaded_message_loop这个参数设置为true的话,那么进程就会创建一个新的UI线程作为Browser管理线程(上一篇有提到过)。那么Browser之间的消息如何与主线程之间连接上,这就需要自己指定。

上一篇的初始化过程是CEF框架中最简单的例子CEFSimple中的,在另一个例子CEFClient中就有如何使用multi_threaded_message_loop参数的方法(cefcilent_win.cc):

  // Create the main context object.scoped_ptr<MainContextImpl> context(new MainContextImpl(command_line, true));CefSettings settings;// Create the main message loop object.scoped_ptr<MainMessageLoop> message_loop;if (settings.multi_threaded_message_loop)message_loop.reset(new MainMessageLoopMultithreadedWin);else if (settings.external_message_pump)message_loop = MainMessageLoopExternalPump::Create();elsemessage_loop.reset(new MainMessageLoopStd);// Initialize CEF.context->Initialize(main_args, settings, app, sandbox_info);// Register scheme handlers.test_runner::RegisterSchemeHandlers();RootWindowConfig window_config;window_config.always_on_top = command_line->HasSwitch(switches::kAlwaysOnTop);window_config.with_controls =!command_line->HasSwitch(switches::kHideControls);window_config.with_osr = settings.windowless_rendering_enabled ? true : false;// Create the first window.context->GetRootWindowManager()->CreateRootWindow(window_config);// Run the message loop. This will block until Quit() is called by the// RootWindowManager after all windows have been destroyed.int result = message_loop->Run();// Shut down CEF.context->Shutdown();// Release objects in reverse order of creation.message_loop.reset();context.reset();

这段代码复杂一些,但其实基本逻辑还是和上一篇提到的CEFSimple的代码逻辑是一样的。

  • cefclient把MainContextImpl弄出来做了一些设定,而cefsimple中使用的是默认值。
  • 另外一个关键点就是cefsimple中使用了chromium中最基本的消息循环,RunLoop.run()来启动线程中的循环。而cefclient中是对这个做了一些包装,然后再启动这个RunLoop.run()来启动循环,这些包装就是如何与主线程进行沟通的方法。
  • 有两个参数来确定具体使用什么样的循环方式:multi_threaded_message_loop与external_message_pump。

multi_threaded_message_loop

这种方式上面已经提到了,就是把UI线程与进程的主线程分开。在cefclient里使用的是MainMessageLoopMultithreadedWin类(在CEF LIB的main_message_loop_multithreaded_win.cc)。

这个类是继承的MainMessageLoop, 这个是一个接口类。
class MainMessageLoopMultithreadedWin : public MainMessageLoop {…}

MainMessageLoopMultithreadedWin定义的Run方法为:

int MainMessageLoopMultithreadedWin::Run() {DCHECK(RunsTasksOnCurrentThread());HINSTANCE hInstance = ::GetModuleHandle(nullptr);{base::AutoLock lock_scope(lock_);// Create the hidden window for message processing.message_hwnd_ = CreateMessageWindow(hInstance);CHECK(message_hwnd_);// Store a pointer to |this| in the window's user data.SetUserDataPtr(message_hwnd_, this);// Execute any tasks that are currently queued.while (!queued_tasks_.empty()) {PostTaskInternal(queued_tasks_.front());queued_tasks_.pop();}}HACCEL hAccelTable =LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_MAINFRAME));MSG msg;// Run the application message loop.while (GetMessage(&msg, nullptr, 0, 0)) {// Allow processing of dialog messages.if (dialog_hwnd_ && IsDialogMessage(dialog_hwnd_, &msg)) {continue;}if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {TranslateMessage(&msg);DispatchMessage(&msg);}}{base::AutoLock lock_scope(lock_);// Destroy the message window.DestroyWindow(message_hwnd_);message_hwnd_ = nullptr;}return static_cast<int>(msg.wParam);
}

搞过WIN32程序的朋友一下就可以看出,下面的这段代码就是从windows的消息队列中拿取消息,接入windows的消息循环,把UI线程和主线程关联起来,相当于把这个线程也并入到操作系统的消息队列中去。

while (GetMessage(&msg, nullptr, 0, 0)) {// Allow processing of dialog messages.if (dialog_hwnd_ && IsDialogMessage(dialog_hwnd_, &msg)) {continue;}if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {TranslateMessage(&msg);DispatchMessage(&msg);}}

external_message_pump

这个参数是一个不太常用的参数,我大致了解了一下,没有细琢磨。我自己的理解是,这个参数可以由自己来写逻辑,控制chromium中的task什么时候进行分发和处理。

还是看代码吧。

使用的是一个叫MainMessageLoopExternalPump的类,有些版本还区分了MainMessageLoopExternalPumpWin/MainMessageLoopExternalPumpLinux/MainMessageLoopExternalPumpMac作为继承细分。不重要,我们以MainMessageLoopExternalPumpWin和MainMessageLoopExternalPump为主。

MainMessageLoopExternalPumpWin的Run函数:

int MainMessageLoopExternalPumpWin::Run() {// Run the message loop.MSG msg;while (GetMessage(&msg, nullptr, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}KillTimer();// We need to run the message pump until it is idle. However we don't have// that information here so we run the message loop "for a while".for (int i = 0; i < 10; ++i) {// Do some work.CefDoMessageLoopWork();// Sleep to allow the CEF proc to do work.Sleep(50);}return 0;
}

同样,还是集成使用了windows的消息队列,然后在没有消息的时候调用的CefDoMessageLoopWork函数十次。

CefDoMessageLoopWork函数:

void CefDoMessageLoopWork() {// Verify that the context is in a valid state.if (!CONTEXT_STATE_VALID()) {DCHECK(false) << "context not valid";return;}// Must always be called on the same thread as Initialize.if (!g_context->OnInitThread()) {DCHECK(false) << "called on invalid thread";return;}base::RunLoop run_loop;run_loop.RunUntilIdle();
}

官方文档上的描述为:Call CefDoMessageLoopWork() on a regular basis instead of calling CefRunMessageLoop(). Each call to CefDoMessageLoopWork() will perform a single iteration of the CEF message loop. Caution should be used with this approach. Calling the method too infrequently will starve the CEF message loop and negatively impact browser performance. Calling the method too frequently will negatively impact CPU usage. See CefBrowserProcessHandler::OnScheduleMessagePumpWork for advanced usage details. You can test this mode in cefclient by running with the “–external-message-pump” command-line flag.

结合描述和代码来看,我觉得就是得空将chromium的消息队列全部清空一次(RunUntilIdle)。具体内容还有待进一步研究。

MainMessageLoopStd

还有最基本的一类,CEF也做了一下封装,其实就是普通的RunLoop类的一种封装:

MainMessageLoopStd::MainMessageLoopStd() {}int MainMessageLoopStd::Run() {CefRunMessageLoop();return 0;
}void MainMessageLoopStd::Quit() {CefQuitMessageLoop();
}void MainMessageLoopStd::PostTask(CefRefPtr<CefTask> task) {CefPostTask(TID_UI, task);
}bool MainMessageLoopStd::RunsTasksOnCurrentThread() const {return CefCurrentlyOn(TID_UI);
}

实验

我在cefclient中做了一个实验,在bing_testing.cc中的OnQuery函数中增加了输出当前线程号的代码:

std::cout << "Thread ID is: " << GetCurrentThreadId() << std::endl;

结果为:

也就是还是同一线程处理。

这篇关于CEF线程之multi_threaded_message_loop参数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/546570

相关文章

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2