Electron多進(jìn)程工具開(kāi)發(fā)日記2:進(jìn)程管理UI

>>博客原文

文中實(shí)現(xiàn)的部分工具方法正處于早期/測(cè)試階段,仍在持續(xù)優(yōu)化中,僅供參考...

在Ubuntu20.04上進(jìn)行開(kāi)發(fā)/測(cè)試,可直接用于Electron項(xiàng)目,測(cè)試版本:Electron@8.2.0 / 9.3.5

Contents


├── Contents (you are here!)
│
├── I. 前言
├── II. 架構(gòu)圖
│
├── III.electron-re 可以用來(lái)做什么?
│   ├── 1) 用于Electron應(yīng)用
│   └── 2) 用于Electron/Nodejs應(yīng)用
│
├── IV.UI功能介紹
│   ├── 主界面
│   ├── 功能1:Kill進(jìn)程
│   ├── 功能2:一鍵開(kāi)啟DevTools
│   ├── 功能3:查看進(jìn)程日志
│   └── 功能4:查看進(jìn)程CPU/Memory占用趨勢(shì)
│
├── V. 使用&原理
│   ├── 引入
│   ├── 怎樣捕獲進(jìn)程資源占用?
│   ├── 怎樣在主進(jìn)程和UI之間共享數(shù)據(jù)?
│   └── 怎樣在UI窗口中繪制折線圖?
│
├── VI. 存在的已知問(wèn)題
│
├── VII. Next To Do
│
├── VIII. 幾個(gè)實(shí)際使用示例
│   ├── 1) Service/MessageChannel示例
│   ├── 2) ChildProcessPool/ProcessHost示例
│   └── 3) test測(cè)試目錄示例

I. 前言


最近在做一個(gè)多文件分片并行上傳模塊的時(shí)候(基于Electron和React),遇到了一些性能問(wèn)題,主要體現(xiàn)在:前端同時(shí)添加大量文件(1000-10000)并行上傳時(shí)(文件同時(shí)上傳數(shù)默認(rèn)為6),在不做懶加載優(yōu)化的情況下,引起了整個(gè)應(yīng)用窗口的卡頓。所以針對(duì)Electron/Nodejs多進(jìn)程這方面做了一些學(xué)習(xí),嘗試使用多進(jìn)程架構(gòu)對(duì)上傳流程進(jìn)行優(yōu)化。

同時(shí)也編寫了一個(gè)方便進(jìn)行Electron/Node多進(jìn)程管理和調(diào)用的工具electron-re,已經(jīng)發(fā)布為npm組件,可以直接安裝:

>> github地址

$: npm install electron-re --save
# or
$: yarn add electron-re

前文《Electron/Node多進(jìn)程工具開(kāi)發(fā)日記》描述了electron-re的開(kāi)發(fā)背景、針對(duì)的問(wèn)題場(chǎng)景以及詳細(xì)的使用方法,這篇文章不會(huì)對(duì)它的基礎(chǔ)使用做過(guò)多說(shuō)明,主要介紹新特性多進(jìn)程管理UI的開(kāi)發(fā)相關(guān)。UI界面基于electron-re已有的BrowserService/MessageChannelChildProcessPool/ProcessHost基礎(chǔ)架構(gòu)驅(qū)動(dòng),使用React17 / Babel7開(kāi)發(fā),主界面:

process-manager.main.png

II. electron-re架構(gòu)圖


electron-re.png

III. electron-re 可以用來(lái)做什么?


1. 用于Electron應(yīng)用

  • BrowserService
  • MessageChannel
  • ProcessManager

在Electron的一些“最佳實(shí)踐”中,建議將占用cpu的代碼放到渲染過(guò)程中而不是直接放在主過(guò)程中,這里先看下chromium的架構(gòu)圖:

chromium.jpg

每個(gè)渲染進(jìn)程都有一個(gè)全局對(duì)象RenderProcess,用來(lái)管理與父瀏覽器進(jìn)程的通信,同時(shí)維護(hù)著一份全局狀態(tài)。瀏覽器進(jìn)程為每個(gè)渲染進(jìn)程維護(hù)一個(gè)RenderProcessHost對(duì)象,用來(lái)管理瀏覽器狀態(tài)和與渲染進(jìn)程的通信。瀏覽器進(jìn)程和渲染進(jìn)程使用Chromium的IPC系統(tǒng)進(jìn)行通信。在chromium中,頁(yè)面渲染時(shí),UI進(jìn)程需要和main process不斷的進(jìn)行IPC同步,若此時(shí)main process忙,則UIprocess就會(huì)在IPC時(shí)阻塞。所以如果主進(jìn)程持續(xù)進(jìn)行消耗CPU時(shí)間的任務(wù)或阻塞同步IO的任務(wù)的話,就會(huì)在一定程度上阻塞,從而影響主進(jìn)程和各個(gè)渲染進(jìn)程之間的IPC通信,IPC通信有延遲或是受阻,渲染進(jìn)程窗口就會(huì)卡頓掉幀,嚴(yán)重的話甚至?xí)ㄗ〔粍?dòng)。

因此electron-re在Electron已有的Main Process主進(jìn)程和Renderer Process渲染進(jìn)程邏輯的基礎(chǔ)上獨(dú)立出一個(gè)單獨(dú)的Service概念。Service即不需要顯示界面的后臺(tái)進(jìn)程,它不參與UI交互,單獨(dú)為主進(jìn)程或其它渲染進(jìn)程提供服務(wù),它的底層實(shí)現(xiàn)為一個(gè)允許node注入remote調(diào)用的渲染窗口進(jìn)程。

這樣就可以將代碼中耗費(fèi)cpu的操作(比如文件上傳中維護(hù)一個(gè)數(shù)千個(gè)上傳任務(wù)的隊(duì)列)編寫成一個(gè)單獨(dú)的js文件,然后使用BrowserService構(gòu)造函數(shù)以這個(gè)js文件的地址path為參數(shù)構(gòu)造一個(gè)Service實(shí)例,從而將他們從主進(jìn)程中分離。如果你說(shuō)那這部分耗費(fèi)cpu的操作直接放到渲染窗口進(jìn)程可以嘛?這其實(shí)取決于項(xiàng)目自身的架構(gòu)設(shè)計(jì),以及對(duì)進(jìn)程之間數(shù)據(jù)傳輸性能損耗和傳輸時(shí)間等各方面的權(quán)衡,創(chuàng)建一個(gè)Service的簡(jiǎn)單示例:

const { BrowserService } = require('electron-re');
const myServcie = new BrowserService('app', path.join(__dirname, 'path/to/app.service.js'));

如果使用了BrowserService的話,要想在主進(jìn)程、渲染進(jìn)程、service進(jìn)程之間任意發(fā)送消息就要使用electron-re提供的MessageChannel通信工具,它的接口設(shè)計(jì)跟Electron內(nèi)建的ipc基本一致,也是基于ipc通信原理來(lái)實(shí)現(xiàn)的,簡(jiǎn)單示例如下:

/* ---- main.js ---- */
const { BrowserService } = require('electron-re');
// 主進(jìn)程中向一個(gè)service-app發(fā)送消息
MessageChannel.send('app', 'channel1', { value: 'test1' });

2. 用于Electron/Nodejs應(yīng)用

  • ChildProcessPool
  • ProcessHost

此外,如果要?jiǎng)?chuàng)建一些不依賴于Electron運(yùn)行時(shí)的子進(jìn)程(相關(guān)參考nodejs child_process),可以使用electron-re提供的專門為nodejs運(yùn)行時(shí)編寫的進(jìn)程池ChildProcessPool類。因?yàn)閯?chuàng)建進(jìn)程本身所需的開(kāi)銷很大,使用進(jìn)程池來(lái)重復(fù)利用已經(jīng)創(chuàng)建了的子進(jìn)程,將多進(jìn)程架構(gòu)帶來(lái)的性能效益最大化,簡(jiǎn)單示例如下:


const { ChildProcessPool } = require('electron-re');

global.ipcUploadProcess = new ChildProcessPool({

  path: path.join(app.getAppPath(), 'app/services/child/upload.js'), max: 6

});

一般情況下,在我們的子進(jìn)程執(zhí)行文件中(創(chuàng)建子進(jìn)程時(shí)path參數(shù)指定的腳本),如要想在主進(jìn)程和子進(jìn)程之間同步數(shù)據(jù),可以使用process.send('channel', params)process.on('channel', function)來(lái)實(shí)現(xiàn)(前提是進(jìn)程以以fork方式創(chuàng)建或者手動(dòng)開(kāi)啟了ipc通信)。但是這樣在處理業(yè)務(wù)邏輯的同時(shí)也強(qiáng)迫我們?nèi)リP(guān)注進(jìn)程之間的通信,你需要知道子進(jìn)程什么時(shí)候能處理完畢,然后再使用process.send再將數(shù)據(jù)返回主進(jìn)程,使用方式繁瑣。

electron-re引入了ProcessHost的概念,我稱之為"進(jìn)程事務(wù)中心"。實(shí)際使用時(shí)在子進(jìn)程執(zhí)行文件中只需要將各個(gè)任務(wù)函數(shù)通過(guò)ProcessHost.registry('task-name', function)注冊(cè)成多個(gè)被監(jiān)聽(tīng)的事務(wù),然后配合進(jìn)程池的ChildProcessPool.send('task-name', params)來(lái)觸發(fā)子進(jìn)程事務(wù)邏輯的調(diào)用即可,ChildProcessPool.send()同時(shí)會(huì)返回一個(gè)Promise實(shí)例以便獲取回調(diào)數(shù)據(jù),簡(jiǎn)單示例如下:

/* --- 主進(jìn)程中 --- */
...
global.ipcUploadProcess
  .send('task1', params)
  .then(rsp => console.log(rsp));

/* --- 子進(jìn)程中 --- */
const { ProcessHost } = require('electron-re');
ProcessHost
  .registry('task1', (params) => {
    return { value: 'task-value' };
  })
  .registry('init-works', (params) => {
    return fetch(url);
  });

IV. UI功能介紹


II 描述了electron-re的主要功能,基于這些功能來(lái)實(shí)現(xiàn)多進(jìn)程監(jiān)控UI面板

主界面

UI參考electron-process-manager設(shè)計(jì)

預(yù)覽圖:


process-manager.main.png

主要功能如下:

  1. 展示Electron應(yīng)用中所有開(kāi)啟的進(jìn)程,包括主進(jìn)程、普通的渲染進(jìn)程、Service進(jìn)程(由electron-re引入)、ChildProcessPool創(chuàng)建的子進(jìn)程(由electron-re引入)。
  2. 進(jìn)程列表中顯示各個(gè)進(jìn)程進(jìn)程號(hào)、進(jìn)程標(biāo)識(shí)、父進(jìn)程號(hào)、內(nèi)存占用大小、CPU占用百分比等,所有進(jìn)程標(biāo)識(shí)分為:main(主進(jìn)程)、service(服務(wù)進(jìn)程)、renderer(渲染進(jìn)程)、node(進(jìn)程池子進(jìn)程),點(diǎn)擊表格頭可以針對(duì)對(duì)某項(xiàng)進(jìn)行遞增/遞減排序。
  3. 選中某個(gè)進(jìn)程后可以Kill此進(jìn)程、查看進(jìn)程控制臺(tái)Console數(shù)據(jù)、查看1分鐘內(nèi)進(jìn)程CPU/內(nèi)存占用趨勢(shì),如果此進(jìn)程是渲染進(jìn)程的話還可以通過(guò)DevTools按鈕一鍵打開(kāi)內(nèi)置調(diào)試工具。
  4. ChildProcessPool創(chuàng)建的子進(jìn)程暫不支持直接打開(kāi)DevTools進(jìn)行調(diào)試,不過(guò)由于創(chuàng)建子進(jìn)程時(shí)添加了--inspect參數(shù),可以使用chrome的chrome://inspect進(jìn)行遠(yuǎn)程調(diào)試。

功能1:Kill進(jìn)程

kill.gif

功能2:一鍵開(kāi)啟DevTools

devtools.gif

功能3:查看進(jìn)程日志

console.gif

功能3:查看進(jìn)程CPU/Memory占用趨勢(shì)

trends.gif
trends2.gif

V.使用&原理


引入

  1. 在Electron主進(jìn)程入口文件中引入:
const {
  MessageChannel, // must required in main.js even if you don't use it
  ProcessManager
} = require('electron-re');
  1. 開(kāi)啟進(jìn)程管理窗口UI

ProcessManager.openWindow();

怎樣捕獲進(jìn)程資源占用?

1.使用ProcessManager監(jiān)聽(tīng)多個(gè)進(jìn)程號(hào)

  • 1)在Electron窗口創(chuàng)建事件中將窗口進(jìn)程id放入ProcessManager監(jiān)聽(tīng)列表
/* --- src/index.js --- */
...
app.on('web-contents-created', (event, webContents) => {
  webContents.once('did-finish-load', () => {
    const pid = webContents.getOSProcessId();
    if (
      exports.ProcessManager.processWindow &&
      exports.ProcessManager.processWindow.webContents.getOSProcessId() === pid
    ) { return; }

    exports.ProcessManager.listen(pid, 'renderer');

    webContents.once('closed', function(e) {
      exports.ProcessManager.unlisten(this.pid);
    }.bind({ pid }));
      ...
  })
});
  • 2)在進(jìn)程池fork子進(jìn)程時(shí)將進(jìn)程id放入監(jiān)聽(tīng)列表
/* --- src/libs/ChildProcessPool.class.js --- */
...
const { fork } = require('child_process');

class ChildProcessPool {
  constructor({ path, max=6, cwd, env }) {
    ...
    this.event = new EventEmitter();
    this.event.on('fork', (pids) => {
      ProcessManager.listen(pids, 'node');
    });

    this.event.on('unfork', (pids) => {
      ProcessManager.unlisten(pids);
    });
  }

  /* Get a process instance from the pool */
  getForkedFromPool(id="default") {
    let forked;
    ...
    forked = fork(this.forkedPath, ...);
    this.event.emit('fork', this.forked.map(fork => fork.pid));
    ...
    return forked;
  }
  ...
}
  • 3)在Service進(jìn)程注冊(cè)時(shí)監(jiān)聽(tīng)進(jìn)程id
    BrowserService進(jìn)程創(chuàng)建時(shí)會(huì)向主進(jìn)程MessageChannel發(fā)送registry請(qǐng)求來(lái)全局注冊(cè)一個(gè)Service服務(wù),此時(shí)將進(jìn)程id放入監(jiān)聽(tīng)列表即可:
/* --- src/index.js --- */
...
exports.MessageChannel.event.on('registry', ({pid}) => {
  exports.ProcessManager.listen(pid, 'service');
});
...

exports.MessageChannel.event.on('unregistry', ({pid}) => {
  exports.ProcessManager.unlisten(pid)
});

2.使用兼容多平臺(tái)的pidusage庫(kù)每秒采集一次進(jìn)程的負(fù)載數(shù)據(jù):

/* --- src/libs/ProcessManager.class.js --- */
...
const pidusage = require('pidusage');

class ProcessManager {
  constructor() {
    this.pidList = [process.pid];
    this.typeMap = {
      [process.pid]: 'main',
    };
    ...
  }

  /* -------------- internal -------------- */

  /* 設(shè)置外部庫(kù)采集并發(fā)送到UI進(jìn)程 */
  refreshList = () => {
    return new Promise((resolve, reject) => {
      if (this.pidList.length) {
        pidusage(this.pidList, (err, records) => {
          if (err) {
            console.log(`ProcessManager: refreshList -> ${err}`);
          } else {
            this.processWindow.webContents.send('process:update-list', { records, types: this.typeMap });
          }
          resolve();
        });
      } else {
        resolve([]);
      }
    });
  }

  /* 設(shè)置定時(shí)器進(jìn)行采集 */
  setTimer() {
    if (this.status === 'started') return console.warn('ProcessManager: the timer is already started!');
    const interval = async () => {
      setTimeout(async () => {
        await this.refreshList()
        interval(this.time)
      }, this.time)
    }
    this.status = 'started';
    interval()
  }
  ...

3.監(jiān)聽(tīng)進(jìn)程輸出來(lái)采集進(jìn)程日志

進(jìn)程池創(chuàng)建的子進(jìn)程可以通過(guò)監(jiān)聽(tīng)stdout標(biāo)準(zhǔn)輸出流來(lái)進(jìn)行日志采集;Electron渲染窗口進(jìn)程則可以通過(guò)監(jiān)聽(tīng)ipc通信事件console-message來(lái)進(jìn)行采集;

/* --- src/libs/ProcessManager.class.js --- */
class ProcessManager {
  constructor() {
    ...
  }

  /* pipe to process.stdout */
  pipe(pinstance) {
    if (pinstance.stdout) {
      pinstance.stdout.on(
        'data',
        (trunk) => {
          this.stdout(pinstance.pid, trunk);
        }
      );
    }
  }
  ...
}

/* --- src/index.js --- */
app.on('web-contents-created', (event, webContents) => {
    webContents.once('did-finish-load', () => {
      const pid = webContents.getOSProcessId();
      ...
      webContents.on('console-message', (e, level, msg, line, sourceid) => {
        exports.ProcessManager.stdout(pid, msg);
      });
      ...
    })
  });

怎樣在主進(jìn)程和UI之間共享數(shù)據(jù)?

基于Electron原生ipc異步通信

1.使用ProcessManager向UI渲染窗口發(fā)送日志數(shù)據(jù)

1秒之內(nèi)采集到的所有進(jìn)程的console數(shù)據(jù)會(huì)被臨時(shí)緩存到數(shù)組中,默認(rèn)每秒鐘向UI進(jìn)程發(fā)送一次數(shù)據(jù),然后清空臨時(shí)數(shù)組。

在這里需要注意的是ChildProcessPool中的子進(jìn)程是通過(guò)Node.js的child_process.fork()方法創(chuàng)建的,此方法會(huì)衍生shell,且創(chuàng)建子進(jìn)程時(shí)參數(shù)stdio會(huì)被指定為'pipe',指明在子進(jìn)程和父進(jìn)程之間創(chuàng)建一個(gè)管道,從而讓父進(jìn)程中可以直接監(jiān)聽(tīng)子進(jìn)程對(duì)象上的 stdout.on('data')事件來(lái)拿到子進(jìn)程的標(biāo)準(zhǔn)輸出流。

/* --- src/libs/ProcessManager.class.js --- */
class ProcessManager {
  constructor() {
    ...
  }

  /* pipe to process.stdout */
  pipe(pinstance) {
    if (pinstance.stdout) {
      pinstance.stdout.on(
        'data',
        (trunk) => {
          this.stdout(pinstance.pid, trunk);
        }
      );
    }
  }

  /* send stdout to ui-processor */
  stdout(pid, data) {
    if (this.processWindow) {
      if (!this.callSymbol) {
        this.callSymbol = true;
        setTimeout(() => {
          this.processWindow.webContents.send('process:stdout', this.logs);
          this.logs = [];
          this.callSymbol = false;
        }, this.time);
      } else {
        this.logs.push({ pid: pid, data: String.prototype.trim.call(data) });
      }
    }
  }
  ...
}

2.使用ProcessManager向UI渲染窗口發(fā)送進(jìn)程負(fù)載信息

/* --- src/libs/ProcessManager.class.js --- */
class ProcessManager {
  constructor() {
    ...
  }

  /* 設(shè)置外部庫(kù)采集并發(fā)送到UI進(jìn)程 */
  refreshList = () => {
    return new Promise((resolve, reject) => {
      if (this.pidList.length) {
        pidusage(this.pidList, (err, records) => {
          if (err) {
            console.log(`ProcessManager: refreshList -> ${err}`);
          } else {
            this.processWindow.webContents.send('process:update-list', { records, types: this.typeMap });
          }
          resolve();
        });
      } else {
        resolve([]);
      }
    });
  }
  ...
}

3.UI窗口拿到數(shù)據(jù)后處理并臨時(shí)存儲(chǔ)

  import { ipcRenderer, remote } from 'electron';
  ...
    ipcRenderer.on('process:update-list', (event, { records, types }) => {
      console.log('update:list');
      const { history } = this.state;
      for (let pid in records) {
        history[pid] = history[pid] || { memory: [], cpu: [] };
        if (!records[pid]) continue;
        history[pid].memory.push(records[pid].memory);
        history[pid].cpu.push(records[pid].cpu);
        // 存儲(chǔ)最近的60條進(jìn)程負(fù)載數(shù)據(jù)
        history[pid].memory = history[pid].memory.slice(-60);
        history[pid].cpu = history[pid].cpu.slice(-60);
      }

      this.setState({
        processes: records,
        history,
        types
      });
    });

    ipcRenderer.on('process:stdout', (event, dataArray) => {
      console.log('process:stdout');
      const { logs } = this.state;
      dataArray.forEach(({ pid, data })=> {
        logs[pid] = logs[pid] || [];
        logs[pid].unshift(`[${new Date().toLocaleTimeString()}]: ${data}`);
      });

      // 存儲(chǔ)最近的1000個(gè)日志輸出
      Object.keys(logs).forEach(pid => {
        logs[pid].slice(0, 1000);
      });
      this.setState({ logs });
    });

怎樣在UI窗口中繪制折線圖?

1.注意使用React.PureComponent,會(huì)自動(dòng)在屬性更新進(jìn)行淺比較,以減少不必要的渲染

/* *************** ProcessTrends *************** */
export class ProcessTrends extends React.PureComponent {
  componentDidMount() {
    ...
  }
  ...
  render() {
    const { visible, memory, cpu } = this.props;
    if (visible) {
      this.uiDrawer.draw();
      this.dataDrawer.draw(cpu, memory);
    };

    return (
      <div className={`process-trends-container ${!visible ? 'hidden' : 'progressive-show' }`}>
        <header>
          <span className="text-button small" onClick={this.handleCloseTrends}>X</span>
        </header>
        <div className="trends-drawer">
          <canvas
            width={document.body.clientWidth * window.devicePixelRatio}
            height={document.body.clientHeight * window.devicePixelRatio}
            id="trendsUI"
          />

          <canvas
            width={document.body.clientWidth * window.devicePixelRatio}
            height={document.body.clientHeight * window.devicePixelRatio}
            id="trendsData"
          />
        </div>
      </div>
    )
  }
}

2.使用兩個(gè)Canvas畫布分別繪制坐標(biāo)軸和折線線段

設(shè)置兩個(gè)畫布相互重疊以盡可能保證靜態(tài)的坐標(biāo)軸不會(huì)被重復(fù)繪制,我們需要在組件掛載后初始化一個(gè)坐標(biāo)軸繪制對(duì)象uiDrawer和一個(gè)數(shù)據(jù)折線繪制對(duì)象dataDrawer

...
  componentDidMount() {
    this.uiDrawer = new UI_Drawer('#trendsUI', {
      xPoints: 60,
      yPoints: 100
    });
    this.dataDrawer = new Data_Drawer('#trendsData');
    window.addEventListener('resize', this.resizeDebouncer);

  }
...

以下是Canvas相關(guān)的基礎(chǔ)繪制命令:

this.canvas = document.querySelector(selector);
this.ctx =  this.canvas.getContext('2d');
this.ctx.strokeStyle = lineColor; // 設(shè)置線段顏色
this.ctx.beginPath(); // 創(chuàng)建一個(gè)新的路徑
this.ctx.moveTo(x, y); // 移動(dòng)到初始坐標(biāo)點(diǎn)(不進(jìn)行繪制)
this.ctx.lineTo(Math.floor(x), Math.floor(y)); // 描述從上一個(gè)坐標(biāo)點(diǎn)到(x, y)的一條直線
this.ctx.stroke(); // 開(kāi)始繪制

繪制類的源代碼可以查看這里Drawer,大概原理是:設(shè)置Canvas畫布寬度width和高度height鋪滿窗口,設(shè)定橫縱坐標(biāo)軸到邊緣的padding值為30,Canvas坐標(biāo)原點(diǎn)[0,0]為繪制區(qū)域左上角頂點(diǎn)。這里以繪制折線圖縱軸坐標(biāo)為例,縱軸表示CPU占用0%-100%或內(nèi)存占用0-1GB,我們可以將縱軸劃分為100個(gè)基礎(chǔ)單位,但是縱軸坐標(biāo)點(diǎn)不用為100個(gè),可以設(shè)置為10個(gè)方便查看,所以每個(gè)坐標(biāo)點(diǎn)就可以表示為[0, (height-padding) - ((height-(2*padding)) / index) * 100 ],index依次等于0,10,20,30...90,其中(height-padding)為最下面那個(gè)坐標(biāo)點(diǎn)位置,(height-(2*padding))為整個(gè)縱軸的長(zhǎng)度。

VI. 存在的已知問(wèn)題


1. 生產(chǎn)環(huán)境下ChildProcessPool未按預(yù)期工作

Electron生產(chǎn)環(huán)境下,如果app被安裝到系統(tǒng)目錄,那么ChildProcessPool不能按照預(yù)期工作,解決辦法有:將app安裝到用戶目錄或者把進(jìn)程池用于創(chuàng)建子進(jìn)程的腳本(通過(guò)path參數(shù)指定)單獨(dú)放到Electron用戶數(shù)據(jù)目錄下(Ubuntu20.04上是~/.config/[appname])。

2. UI界面未監(jiān)聽(tīng)主進(jìn)程Console數(shù)據(jù)

主進(jìn)程暫未支持此功能,正在尋找解決方案。

VII. Next To Do


  • 讓Service支持代碼更新后自動(dòng)重啟

  • 添加ChildProcessPool子進(jìn)程調(diào)度邏輯

  • 優(yōu)化ChildProcessPool多進(jìn)程console輸出

  • 添加可視化進(jìn)程管理界面

  • 增強(qiáng)ChildProcessPool進(jìn)程池功能

  • 增強(qiáng)ProcessHost事務(wù)中心功能

VIII. 幾個(gè)實(shí)際使用示例


  1. electronux - 我的一個(gè)Electron項(xiàng)目,使用了 BrowserService/MessageChannel,并且附帶了ChildProcessPool/ProcessHost使用demo。
  2. file-slice-upload - 一個(gè)關(guān)于多文件分片并行上傳的demo,使用了 ChildProcessPool and ProcessHost,基于 Electron@9.3.5開(kāi)發(fā)。
  3. 也查看 test 目錄下的測(cè)試樣例文件,包含了完整的細(xì)節(jié)使用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容