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

相关文章

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

HTTP 与 SpringBoot 参数提交与接收协议方式

《HTTP与SpringBoot参数提交与接收协议方式》HTTP参数提交方式包括URL查询、表单、JSON/XML、路径变量、头部、Cookie、GraphQL、WebSocket和SSE,依据... 目录HTTP 协议支持多种参数提交方式,主要取决于请求方法(Method)和内容类型(Content-Ty

Java中如何正确的停掉线程

《Java中如何正确的停掉线程》Java通过interrupt()通知线程停止而非强制,确保线程自主处理中断,避免数据损坏,线程池的shutdown()等待任务完成,shutdownNow()强制中断... 目录为什么不强制停止为什么 Java 不提供强制停止线程的能力呢?如何用interrupt停止线程s

RabbitMQ 延时队列插件安装与使用示例详解(基于 Delayed Message Plugin)

《RabbitMQ延时队列插件安装与使用示例详解(基于DelayedMessagePlugin)》本文详解RabbitMQ通过安装rabbitmq_delayed_message_exchan... 目录 一、什么是 RabbitMQ 延时队列? 二、安装前准备✅ RabbitMQ 环境要求 三、安装延时队

python 线程池顺序执行的方法实现

《python线程池顺序执行的方法实现》在Python中,线程池默认是并发执行任务的,但若需要实现任务的顺序执行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录方案一:强制单线程(伪顺序执行)方案二:按提交顺序获取结果方案三:任务间依赖控制方案四:队列顺序消

python中的显式声明类型参数使用方式

《python中的显式声明类型参数使用方式》文章探讨了Python3.10+版本中类型注解的使用,指出FastAPI官方示例强调显式声明参数类型,通过|操作符替代Union/Optional,可提升代... 目录背景python函数显式声明的类型汇总基本类型集合类型Optional and Union(py

Go语言使用Gin处理路由参数和查询参数

《Go语言使用Gin处理路由参数和查询参数》在WebAPI开发中,处理路由参数(PathParameter)和查询参数(QueryParameter)是非常常见的需求,下面我们就来看看Go语言... 目录一、路由参数 vs 查询参数二、Gin 获取路由参数和查询参数三、示例代码四、运行与测试1. 测试编程路

Python lambda函数(匿名函数)、参数类型与递归全解析

《Pythonlambda函数(匿名函数)、参数类型与递归全解析》本文详解Python中lambda匿名函数、灵活参数类型和递归函数三大进阶特性,分别介绍其定义、应用场景及注意事项,助力编写简洁高效... 目录一、lambda 匿名函数:简洁的单行函数1. lambda 的定义与基本用法2. lambda

SpringBoot实现虚拟线程的方案

《SpringBoot实现虚拟线程的方案》Java19引入虚拟线程,本文就来介绍一下SpringBoot实现虚拟线程的方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录什么是虚拟线程虚拟线程和普通线程的区别SpringBoot使用虚拟线程配置@Async性能对比H

在Java中实现线程之间的数据共享的几种方式总结

《在Java中实现线程之间的数据共享的几种方式总结》在Java中实现线程间数据共享是并发编程的核心需求,但需要谨慎处理同步问题以避免竞态条件,本文通过代码示例给大家介绍了几种主要实现方式及其最佳实践,... 目录1. 共享变量与同步机制2. 轻量级通信机制3. 线程安全容器4. 线程局部变量(ThreadL