关于Electron性能的讨论
系列文章
- 什么是Electron
- 如何使用Electron
- Electron的基础知识
- 在Electron中使用Vue
- 关于Electron性能的讨论【当前文章】
- 在Electron中使用C++扩展
五、关于性能的讨论
如第一章的分析,Electron并非毫无缺点。其主要的缺点就是:
-
体积大。
因为需要背着一个浏览器和运行时,即使最简单的HelloWorld,也会有几十MB。这个是无解的。
-
内存消耗比较大。
这个也无解,毕竟需要运行一个完整的浏览器。
-
性能比较弱。
这是因为程序的主要逻辑都是使用JavaScript写的,即使使用的性能最强的V8引擎,脚本语言的运行效率与编译型语言相比依旧比较差。
由于现在的电脑硬盘和内存普遍都比较大,所以,前两个缺点,影响并没有那么的严重。但是,第三个缺点就不得不想办法克服了。这是因为JavaScript是一个单线程的语言。无法发挥现代CPU多核心的优势。遇到计算密集性操作就会出现肉眼可见的卡顿。典型的如数据压缩、加解密等等。很不幸,现代CPU全都是采用多核心来提高效率的。好在,这并非无解。我们可以采取很多种方法优化性能。
首先,我们在demo4
的基础上,做以下几点修改:
App.vue
修改如下:
<template>
<div id="animation"></div> <br>
<button @click="clac(10 * 1000)">运算</button> <br><br>
<div id="log"></div>
</template>
<script>
export default {
name: 'App',
methods: {
clac(time) {
var timeStamp = new Date().getTime();
var endTime = timeStamp + time;
document.querySelector("#log").innerHTML += timeStamp + ": begin clac " + time + "<br>";
while (new Date().getTime() < endTime) {
timeStamp = new Date().getTime();
}
timeStamp = new Date().getTime();
document.querySelector("#log").innerHTML += timeStamp + ": finish clac " + time + "<br>";
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 10px;
text-align: left;
color: #2c3e50;
margin-top: 60px;
}
#animation {
width: 100px;
height: 100px;
background-color: red;
animation-name: example;
animation-duration: 0.5s;
animation-direction: alternate-reverse;
animation-iteration-count: infinite;
}
@keyframes example {
from {
background-color: green;
}
to {
background-color: red;
}
}
</style>
-
在模板中新增两个div,其中,animation是一个循环动画,log用来显示日志信息。
-
新增了一个按钮,当按钮被点击时,会执行
clac
函数,我们在这个函数中进行10秒的模拟运算。
于是,当点击运算
按钮的时候,你就会发现,动画暂停了。不仅动画停了,其实这个时候整个渲染进程都被运算
函数所占用了,整个界面处于卡死状态。
5.1 Web Worker
在H5中,新增了一个Web Worker
的特性。它提供了一种简单的方法,让我们可以把大量运算的操作放到后台来做。这样线程可以执行任务而不干扰用户界面。
于是,我们可以App.vue
中的clac
函数改为:
clac(time) {
const myWorker = new Worker("worker.js");
myWorker.onmessage = (e) => {
document.querySelector("#log").innerHTML += e.data.begin + ": begin clac " + time + "<br>";
document.querySelector("#log").innerHTML += e.data.end + ": finish clac " + time + "<br>";
console.log("Message received from worker");
};
myWorker.postMessage(time);
}
首先,创建一个Worker,运行脚本worker.js
。
然后,定义一个消息处理函数,以便接收worker线程发送过来的结果。
最后,我们给工作线程发送一个消息,让工作线程开始干活。
接下来,在public
目录新增一个名为worker.js
的脚本,内容如下:
do_clac = (time) => {
var timeStamp = new Date().getTime();
var endTime = timeStamp + time;
while (new Date().getTime() < endTime) {
timeStamp = new Date().getTime();
}
var end = new Date().getTime();
return { begin: timeStamp, end: end }
}
onmessage = (e) => {
console.log("Message received from main script: " + e.data);
let result = do_clac(e.data)
console.log("Posting message back to main script");
postMessage(result);
};
这个脚本也不复杂,主要就是两个函数,
do_clac
,这个函数跟之前写在App.vue
里面的clac
几乎相同。唯一不同的是,在这儿我们无法直接操作dom,所以,我们把结果返回出来。onmessage
,这个函数用来接收主线程发送过来的消息。当执行完成后,在使用postMessage
将结果发送给主线程。
使用了Worker以后,再次点击运算
按钮。你就会发现,这次动画可以正常运行了。我们做了大量运算,但是,并不会导致界面卡顿。
Worker是一个非常好的特性,合理的使用它可以让我们的程序有效使用CPU的多核算力。这儿只是简单的演示,更详细的用法可以参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers#web_workers_api
5.2 C++扩展
虽然,我们有了Web Worker可以用来做一些计算密集型的操作而不影响界面的流畅运行。但是,依旧存在一些任务无法在JavaScript里面完成。例如,视频编解码、数据压缩和解压缩等等。又或者授权检查,如果用JavaScript来写,就会很容易被破解。这个时候,我们就可以使用C/C++来扩展Electron了。
可能有人会有疑问,使用了C/C++是不是就会导致跨平台能力降低?其实,不会。无法跨平台的不是C/C++语言,而是GUI和操作系统接口部分。如果我们不用C/C++来调用操作系统的接口,就不会存在跨平台问题了。
5.2.1 编译环境
C/C++是编译型语言,所以,使用C/C++之前,首先需要准备好环境。不过,并不复杂。只需要安装一个node-gyp
这个包就可以了。
npm install -g node-gyp
除了node-gyp,你还需要有一整套C/C++编译工具。
如果是Windows系统,需要提前安装一下Visual Studio
和Python
。这两个都可以手动安装,安装方法可以在网上找一下,直接去官网下载安装包安装一下即可。如果想偷懒也可以使用Chocolatey,这是一个Windows下的软件包管理器,如同Linux下的Apt和yum,可以做到一键安装:
choco install python visualstudio2022-workload-vctools -y
另外,需要特别留意的是,Windows下需要用到Powershell
。
在Linux下是最简单的只需要一个命令就可以完成安装包的依赖,如果是Debian系,就是使用
sudo apt install -y build-essential python3 make
如果是MacOS,则需要使用xcode-select --install
命令安装Xcode Command Line Tools
。
5.2.2 binding.gyp
首先,需要创建一个binding.gyp
的文件,内容如下:
{
"targets": [
{
"target_name": "hello",
"sources": [ "hello.cpp" ]
}
]
}
内容比较简单,就是定义一下最基本的信息。其中可以包含以下信息:
target_name:目标的名称,此名称将用作生成的 Visual Studio 解决方案中的项目名称。
type:可选项:static_library 静态库、executable 可执行文件、shared_library 共享库【默认】。
defines:将在编译命令行中传入的 C 预处理器定义(使用 -D 或 /D 选项)。
include_dirs:C++ 头文件所在的目录。
sources:C++ 源文件。
conditions:适配不同环境配置条件块。
copies:拷贝 dll 动态库到生成目录。
library_dirs: 配置 lib 库目录到 vs 项目中。
libraries:项目依赖的库。
msvs_settings:Visual Studio 中属性设置。
相对比较完整的例子如下:
{
"targets": [
{
"target_name": "addon_name",
"type": "static_library",
'defines': ['DEFINE_FOO', 'DEFINE_A_VALUE=value',],
'include_dirs': [
'./src/include',
'<!(node -e "require(\'nan\')")' # include NAN in your project
],
'sources': [
'file1.cc',
'file2.cc',
],
'conditions': [[
'OS=="win"', {
'copies': [{
'destination': '<(PRODUCT_DIR)',
'files': ['./dll/*']
}],
'defines': ['WINDOWS_SPECIFIC_DEFINE',],
'library_dirs': ['./lib/'],
'link_settings': {
'libraries': ['-lyou_sdk.lib']
},
'msvs_settings': {
'VCCLCompilerTool': {
'AdditionalOptions': ['/utf-8']
}
},
}
]],
},
]
}
当然,在咱们这个例子中由于没有任务依赖,所以,不需要用到这么复杂的配置。
5.2.3 C++源文件
hello.cpp
文件的内容为:
#include <node.h>
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Value;
void Hello(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world", NewStringType::kNormal).ToLocalChecked());
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "hello", Hello);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
}
其中,
NODE_MODULE
:这个用来定义一个Node模块的宏。当Node加载该模块的时候,就会按照这个宏的定义调用Initialize
函数。Initialize
函数的功能是导出符号。可以导出函数和常量两种符号。我们这儿仅仅导出了一个名为hello
的函数,然后,其实现函数为Hello
。- 在
Hello
函数中,所有的入参和出参都是通过args
完成的。在这个例子中,我们仅仅返回了一个字符串world
。
5.2.4 调用C++扩展
我们只能在主进程中调用C++扩展,在渲染进程中,如果想使用C++扩展,就必须通过ipc调用完成。主进程运行的脚本index.js
内容如下:
let { app, BrowserWindow, ipcMain } = require('electron');
let hello_file = "./build/Release/hello";
const hello = require(hello_file);
ipcMain.on("hello", (event) => {";
console.log("call hello in main process");
event.reply("hello_reply", hello.hello());
});
app.whenReady().then(() => {
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
win.loadFile("./index.html")
})
在渲染进程中通过ipcRenderer来调用主进程,index.html
的内容如下:
<!DOCTYPE html>
<html>
<head>
<title>demo for c++ in electron</title>
</head>
<body>
<button onclick="sendMsg()">发送消息</button>
<br>
<div id="txt"></div>
<script lang="javascript">
let { ipcRenderer } = require("electron");
ipcRenderer.on("hello_reply", (event, msg) => {
document.querySelector("#txt").innerHTML += msg + "<br>";
})
function sendMsg() {
ipcRenderer.send("hello");
}
</script>
</body>
</html>
5.2.5 编译和使用
有了这两个文件就可以进行编译了。运行下面这个命令即可:
node-gyp configure # 生成配置项,仅需要运行一次。
node-gyp build # 编译
如果此次你运行,就无法发现,编译出来的C++扩展无法使用。报错信息大致如下:
App threw an error during load
Error: The module '/home/allan/Desktop/Electronå
¥é¨ç¯/demo5.1/build/Release/hello.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 93. This version of Node.js requires
NODE_MODULE_VERSION 101. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
at process.func [as dlopen] (node:electron/js2c/asar_bundle:5:1812)
at Object.Module._extensions..node (node:internal/modules/cjs/loader:1199:18)
at Object.func [as .node] (node:electron/js2c/asar_bundle:5:1812)
at Module.load (node:internal/modules/cjs/loader:988:32)
at Module._load (node:internal/modules/cjs/loader:829:12)
at Function.c._load (node:electron/js2c/asar_bundle:5:13343)
at Module.require (node:internal/modules/cjs/loader:1012:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/home/allan/Desktop/Electron入门篇/demo5.1/index.js:4:15)
at Module._compile (node:internal/modules/cjs/loader:1116:14)
A JavaScript error occurred in the main process
这是因为,我们编译的node.js
与Electron
中集成的node.js
版本不一致导致的。node-gyp
编译时使用的是编译环境中的node.js
,而Electron
中集成的node.js
与编译环境中总是会有一些不同。所以,无法在Electron
中正常加载使用。解决方法也容易,那就是使用electron-rebuild
这个包来编译。
electron-rebuild
的安装方法为:
npm add electron-rebuild --save-dev
为了方便使用,我们可以改一下package.json
,内容如下:
{
"name": "demo5.1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "electron .",
"build": "electron-rebuild"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "17.3.1",
"electron-rebuild": "^3.2.9"
}
}
其中,build
命令就是用来替代node-gyp
命令进行编译的。electron-rebuild
会解决版本不一致问题,然后调用node-gyp
完成编译。它解决版本不一致的方法其实是使用Electron
提供的头文件,而不是使用node.js
提供的头文件。而且会有一些版本上的差异,功能上并无区别。
需要留意的是,默认情况下node-gpy
会自动去https://nodejs.org/dist
下载node.js的头文件,而electron-rebuild
会去https://artifacts.electronjs.org/headers/
下载头文件。不过,electronjs
中不允许访问目录,只能通过文件完整路径去下载。
接下来,我们只需要执行下面两个命令就可以了:
npm run build # 编译
npm run start # 运行
不出意外的话,你将看到下面的画面:
关于C++扩展的参考资料:
5.2.6 Vue中使用C++扩展
如果想在vue中使用C++扩展,事情会变得稍微复杂一些。这是因为vue插件做了很多工作,导致我们无法直接通过require
来加载C++编译出来的node文件了,这其实是封装带来的坏处。不过,并非做不到,只是没有那么的直白了而已。
首先,我们按照第4章中的步骤创建一个vue的工程出来,然后,在工程目录中在创建一个名为:hello_addon
的目录。在该目录中执行以下命令:
npm init --yes
然后,创建两个文件,分别是binding.gyp
、hello.cpp
和index.js
。其中building.gyp
和hello.cpp
的内容跟前面完全相同。代码就不再贴出来了,想看的可以翻看前面的章节或者直接打开demo5.2
的源码看一下。index.js
有了较大不同,内容变为:
module.exports = require('./build/Release/hello_addon');
只有一行,那就是把hello_addon.node
这个扩展导出来,以便使用者调用。到这儿,扩展部分就写好了。接下来,我们回到vue工程的根目录,执行下面这条命令:
vue install ./hello_addon --save
意思就是,把子目录hello_addon
这个包添加到进来vue工程中来。你打开package.json
,就会发现hello_addon
这个依赖包已经被添加进来了。内容大致如下:
... ...
"dependencies": {
"core-js": "^3.8.3",
"hello_addon": "file:hello_addon",
"vue": "^3.2.13"
},
... ...
接下来,我们在background.js
中添加调用hello_addon
的代码,内容如下:
... ...
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
let hello = require("hello_addon")
console.log("call hello_addon in main process: ", hello.hello())
async function createWindow() {
... ...
如果,你这个时候运行npm run electron:serve
这条指令的话,你就会看到下面的错误信息:
即使在这儿我们把hello_addon
的路径改为全路径依旧会报找不到。这是因为我们在运行的时候使用webpack,加载的时候不会再直接加载,而是经过了webpack的处理,导致无法加载成功。为了解决这个问题,我们需要在vue.config.js
中做一点修改,内容如下:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
pluginOptions: {
electronBuilder: {
externals: ["hello_addon"],
},
},
})
其中,第7行是最关键的一行,它的意思是告诉webpack,它是一个外部模块,不要打包它。在官方文档也有相关的说明:
不过,官方的文档并没有说明。这个配置是针对包的,而非文件。所以,很多人会在这个地方出现理解上的偏差,导致卡壳。
改完这个配置文件以后,再次执行npm run electron:serve
就不会再有错误了。
最后。我们通过命令npm run electron:build
打包以后看一下,你会发现打包好的程序也是可以正常使用的。但是,如果你查看一下app.asar
文件就会发现有个不大不小的安全问题:
打包以后的asar文件中居然存在hello.cpp
这个源文件。这是因为webpack并不知道cpp文件不能打包进来。我们可以在vue.config.js
中将这个信息告诉给它。
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
pluginOptions: {
electronBuilder: {
externals: ["hello_addon"],
builderOptions: {
files: [
"**/*",
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
"!**/node_modules/*.d.ts",
"!**/node_modules/.bin",
"!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj,cpp,hpp,c,h}",
"!.editorconfig",
"!**/._*",
"!**/node_gyp_bins",
"!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}",
"!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}",
"!**/{appveyor.yml,.travis.yml,circle.yml}",
"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}"
]
},
},
},
})
其中,第15行就是告诉webpack,这些扩展名的文件不需要打包到asar文件中去的。
5.2.7 C++扩展中的异步
正常而言,我们调用JavaScript的话,是同一线程的,所以,对于性能有提升,但是有限。因为这样做依旧无法有效利用CPU的多核心。于是,我们需要在C++中使用多线程,使用异步处理的方式达到多核心同时运行的效果。既然使用了异步,执行完成后如何把执行完成的结果返回给JavaScript就是一个问题了。
在JavaScript中,有两种形式可以实现异步执行时返回结果。
-
使用
callback
函数。在调用函数时传入一个函数指针,当处理完成后,由异步处理函数来调用这个
callback
函数返回执行结果。 -
使用
Promise
对象。函数正常返回,但是,返回的不是结果,而是一个
Promise
对象,调用者再通过Promise
对象拿到执行的结果。
本质上二者是相同的,但是,由于使用方式的不同,二者又具有不同的特点。其中,callback
函数本身并不代表异步。只是说,如果callback
函数可以用于异步。换句话说,同步也可以使用callback
。另外,callback
很容易出现回调地狱
,也就是因为回调中嵌套回调导致代码变得复杂不容易理解。而Promise
对象不存在这个问题。因为Promise
对象本身是同步的,只有获取Promise
对象中的结果的时候才是异步的。也就避免了嵌套。所以,推荐使用Promise
对象来实现异步。
5.2.7.1 使用callback
函数实现异步
基于demo5.2
,我们把hello
扩展中的cpp改写为:
#include <node.h>
#include "callback.h"
namespace demo
{
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Value;
v8::Global<v8::Function> func_callback;
void worker_thread_func(uv_work_t *req)
{
callback::request_data *request = (callback::request_data *)req->data;
uv_sleep(5000);
request->result = "world";
}
void HelloWithCallback(const FunctionCallbackInfo<Value> &args)
{
v8::Isolate *isolate = args.GetIsolate();
v8::Local<v8::Context> context = v8::Context::New(isolate);
if (!callback::check_request_param(isolate, context, args))
{
args.GetReturnValue().Set(v8::Boolean::New(isolate, false));
return;
}
callback::begin_worker_thread(isolate, context, args, worker_thread_func);
args.GetReturnValue().Set(v8::Boolean::New(isolate, true));
}
void Initialize(Local<Object> exports)
{
NODE_SET_METHOD(exports, "HelloWithCallback", HelloWithCallback);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
}
我们导出了一个名为HelloWithCallback
的函数,这个函数里面并没有实际的逻辑,而是开启了一个worker线程。这个线程的逻辑是在callback.h
里面实现的。
我们把实际的逻辑放在了函数worker_thread_func
里面,这个函数是运行在worker线程中,所以,即使运行很长时间也不会有什么影响。
callback.h
的内容如下:
#include <string>
#include <v8.h>
#include <uv.h>
namespace callback
{
struct request_data
{
std::string argv;
std::string result;
v8::Global<v8::Function> func_callback;
request_data(v8::Isolate *isolate, v8::Local<v8::String> request_json, v8::Local<v8::Function> func)
{
argv = *v8::String::Utf8Value(isolate, request_json);
func_callback.Reset(isolate, func);
}
};
void worker_callbakc_func(uv_work_t *req, int status)
{
if (status == UV_ECANCELED)
{
request_data *request = (request_data *)req->data;
delete req;
request->func_callback.Reset();
delete request;
return;
}
v8::Isolate *isolate = v8::Isolate::GetCurrent();
request_data *request = (request_data *)req->data;
delete req;
// 构造JS回调函数的参数
v8::Local<v8::Value> argv[1];
argv[0] = v8::String::NewFromUtf8(isolate, request->result.c_str()) .ToLocalChecked();
v8::TryCatch try_catch(isolate);
v8::Local<v8::Function> cb = v8::Local<v8::Function>::New(isolate, request->func_callback);
cb->Call(isolate->GetCurrentContext(), Null(isolate), 1, argv);
if (try_catch.HasCaught())
{
fprintf(stderr, "callback failure");
}
// 回收资源
request->func_callback.Reset();
delete request;
}
//检查请求参数
bool check_request_param(v8::Isolate *isolate, v8::Local<v8::Context> &context, const v8::FunctionCallbackInfo<v8::Value> &args)
{
// 判断入参是否满足条件
v8::TryCatch try_catch(isolate);
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction())
return false;
v8::MaybeLocal<v8::Value> jsonData = v8::JSON::Parse(context, args[0]->ToString(context).ToLocalChecked());
if (jsonData.IsEmpty())
return false;
if (try_catch.HasCaught())
return false;
return true;
}
// 启动线程
void begin_worker_thread(v8::Isolate *isolate, v8::Local<v8::Context> &context, const v8::FunctionCallbackInfo<v8::Value> &args, uv_work_cb work_cb)
{
//构造uv_work_t
request_data *req = new request_data(isolate, args[0]->ToString(context).ToLocalChecked(), v8::Local<v8::Function>::Cast(args[1]));
uv_work_t *uv_req = new uv_work_t();
uv_req->data = req;
//启动uv线程
uv_queue_work(uv_default_loop(), uv_req, work_cb, worker_callbakc_func);
}
}
在这个文件中,我们启动的一个uv线程,然后,将参数传入到uv线程中,在uv线程中执行我们函数的功能,然后再把结果通过回调函数返回给JavaScript。
使用方法跟普通的js函数无异:
const hello = require("hello");
ipcMain.on("hello", (event) => {
hello.HelloWithCallback(JSON.stringify({}), (result) => {
event.reply("hello_ack", result);
})
}
5.2.7.2 使用Promise
对象实现异步
同样基于demo5.2
,我们把hello
扩展中的cpp改写为:
#include <node.h>
#include "resolver.h"
namespace demo
{
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Value;
void worker_thread_Resolver(uv_work_t *req)
{
callback::request_data *request = (callback::request_data *)req->data;
uv_sleep(5000);
auto tid = syscall(SYS_gettid);
request->result = "world, " + std::to_string(tid);
}
void HelloAsync(const FunctionCallbackInfo<Value> &args)
{
v8::Isolate *isolate = args.GetIsolate();
v8::Local<v8::Context> context = v8::Context::New(isolate);
Local<v8::Promise::Resolver> resolver = v8::Promise::Resolver::New(context).ToLocalChecked();
resolver::begin_worker_thread(args, resolver, worker_thread_Resolver);
args.GetReturnValue().Set(resolver->GetPromise());
}
void Initialize(Local<Object> exports)
{
NODE_SET_METHOD(exports, "HelloAsync", HelloAsync);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
}
resolver.h
的内容如下:
#include <node.h>
#include <thread>
namespace resolver
{
struct request_data
{
std::string argv;
std::string result;
v8::Global<v8::Promise::Resolver> resolver;
request_data(v8::Isolate *isolate, v8::Local<v8::String> request_json, v8::Local<v8::Promise::Resolver> _resolver)
{
argv = *v8::String::Utf8Value(isolate, request_json);
resolver.Reset(isolate, _resolver);
}
};
using v8::FunctionCallbackInfo;
void worker_callbakc_func(uv_work_t *req, int status)
{
if (status == UV_ECANCELED)
{
request_data *request = (request_data *)req->data;
delete req;
request->resolver.Reset();
delete request;
return;
}
v8::Isolate *isolate = v8::Isolate::GetCurrent();
request_data *request = (request_data *)req->data;
delete req;
v8::TryCatch try_catch(isolate);
v8::Local<v8::Promise::Resolver> resolver = v8::Local<v8::Promise::Resolver>::New(isolate, request->resolver);
v8::Local<v8::Value> value = v8::String::NewFromUtf8(isolate, request->result.c_str()).ToLocalChecked();
resolver->Resolve(isolate->GetCurrentContext(), value);
if (try_catch.HasCaught())
{
fprintf(stderr, "resolver failure");
}
// 回收资源
request->resolver.Reset();
delete request;
}
void begin_worker_thread(const FunctionCallbackInfo<v8::Value> &args, v8::Local<v8::Promise::Resolver> &resolver, uv_work_cb work_cb)
{
v8::Isolate *isolate = args.GetIsolate();
v8::Local<v8::Context> context = v8::Context::New(isolate);
request_data *req = new request_data(isolate, args[0]->ToString(context).ToLocalChecked(), resolver);
uv_work_t *uv_req = new uv_work_t();
uv_req->data = req;
//启动uv线程
uv_queue_work(uv_default_loop(), uv_req, work_cb, worker_callbakc_func);
}
}
可以看到,跟Callback
最大的不同仅仅是把回调函数换成了Promise
对象而已。另外,需要留意的是,无论是CallBack
还是Promise
对象回调回去以后都是在JavaScript的主线程里面。
系列文章
- 什么是Electron
- 如何使用Electron
- Electron的基础知识
- 在Electron中使用Vue
- 关于Electron性能的讨论【当前文章】
- 在Electron中使用C++扩展