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线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程

Java JAR 启动内存参数配置指南(从基础设置到性能优化)

《JavaJAR启动内存参数配置指南(从基础设置到性能优化)》在启动Java可执行JAR文件时,合理配置JVM内存参数是保障应用稳定性和性能的关键,本文将系统讲解如何通过命令行参数、环境变量等方式... 目录一、核心内存参数详解1.1 堆内存配置1.2 元空间配置(MetASPace)1.3 线程栈配置1.

深入理解Redis线程模型的原理及使用

《深入理解Redis线程模型的原理及使用》Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的,整个线程模型可以理解为还是以单线程为主,基于这种单线程为主的线程模型,不同客户端的... 目录1 Redis是单线程www.chinasem.cn还是多线程2 Redis如何保证指令原子性2.

SpringMVC配置、映射与参数处理​入门案例详解

《SpringMVC配置、映射与参数处理​入门案例详解》文章介绍了SpringMVC框架的基本概念和使用方法,包括如何配置和编写Controller、设置请求映射规则、使用RestFul风格、获取请求... 目录1.SpringMVC概述2.入门案例①导入相关依赖②配置web.XML③配置SpringMVC

C++实现一个简易线程池的使用小结

《C++实现一个简易线程池的使用小结》在现代软件开发中,多线程编程已经成为提升程序性能的常见手段,本文主要介绍了C++实现一个简易线程池的使用小结,感兴趣的可以了解一下... 在现代软件开发中,多线程编程已经成为提升程序性能的常见手段。无论是处理大量 I/O 请求的服务器,还是进行 CPU 密集型计算的应用

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Java 线程池+分布式实现代码

《Java线程池+分布式实现代码》在Java开发中,池通过预先创建并管理一定数量的资源,避免频繁创建和销毁资源带来的性能开销,从而提高系统效率,:本文主要介绍Java线程池+分布式实现代码,需要... 目录1. 线程池1.1 自定义线程池实现1.1.1 线程池核心1.1.2 代码示例1.2 总结流程2. J

Java JUC并发集合详解之线程安全容器完全攻略

《JavaJUC并发集合详解之线程安全容器完全攻略》Java通过java.util.concurrent(JUC)包提供了一整套线程安全的并发容器,它们不仅是简单的同步包装,更是基于精妙并发算法构建... 目录一、为什么需要JUC并发集合?二、核心并发集合分类与详解三、选型指南:如何选择合适的并发容器?在多

C#中通过Response.Headers设置自定义参数的代码示例

《C#中通过Response.Headers设置自定义参数的代码示例》:本文主要介绍C#中通过Response.Headers设置自定义响应头的方法,涵盖基础添加、安全校验、生产实践及调试技巧,强... 目录一、基础设置方法1. 直接添加自定义头2. 批量设置模式二、高级配置技巧1. 安全校验机制2. 类型