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

相关文章

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

java父子线程之间实现共享传递数据

《java父子线程之间实现共享传递数据》本文介绍了Java中父子线程间共享传递数据的几种方法,包括ThreadLocal变量、并发集合和内存队列或消息队列,并提醒注意并发安全问题... 目录通过 ThreadLocal 变量共享数据通过并发集合共享数据通过内存队列或消息队列共享数据注意并发安全问题总结在 J

Java通过反射获取方法参数名的方式小结

《Java通过反射获取方法参数名的方式小结》这篇文章主要为大家详细介绍了Java如何通过反射获取方法参数名的方式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、解决方式方式2.1: 添加编译参数配置 -parameters方式2.2: 使用Spring的内部工具类 -

异步线程traceId如何实现传递

《异步线程traceId如何实现传递》文章介绍了如何在异步请求中传递traceId,通过重写ThreadPoolTaskExecutor的方法和实现TaskDecorator接口来增强线程池,确保异步... 目录前言重写ThreadPoolTaskExecutor中方法线程池增强总结前言在日常问题排查中,

mysqld_multi在Linux服务器上运行多个MySQL实例

《mysqld_multi在Linux服务器上运行多个MySQL实例》在Linux系统上使用mysqld_multi来启动和管理多个MySQL实例是一种常见的做法,这种方式允许你在同一台机器上运行多个... 目录1. 安装mysql2. 配置文件示例配置文件3. 创建数据目录4. 启动和管理实例启动所有实例

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

MySQL中时区参数time_zone解读

《MySQL中时区参数time_zone解读》MySQL时区参数time_zone用于控制系统函数和字段的DEFAULTCURRENT_TIMESTAMP属性,修改时区可能会影响timestamp类型... 目录前言1.时区参数影响2.如何设置3.字段类型选择总结前言mysql 时区参数 time_zon

Python如何使用seleniumwire接管Chrome查看控制台中参数

《Python如何使用seleniumwire接管Chrome查看控制台中参数》文章介绍了如何使用Python的seleniumwire库来接管Chrome浏览器,并通过控制台查看接口参数,本文给大家... 1、cmd打开控制台,启动谷歌并制定端口号,找不到文件的加环境变量chrome.exe --rem

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后