Electron怎么启动并行的子任务

Electron怎么启动并行的子任务

有些场景下好比要处理一大堆文件内容的查找,字符串的替换,文件的修改等一系列耗时操做的时候,若是放在主进程执行那必然会致使渲染进程的阻塞,界面会出现卡死的现象,影响用户体验。那怎么能并行地去处理这些事情呢,能够经过node的多进程去实现。若是你在思考把这些任务放在新的渲染进程中去作,那么每次启动一个任务都要打开一个隐藏的窗口,还要考虑去怎么关闭,代价有点高。这里用一个清除缓存的例子介绍下具体步骤,功能很简单就是每次应用启动的时候清理一些指定的文件。node

Node多进程

先简单介绍一下,node的多进程是经过child_process和cluster模块实现。child _process模块中建立子进程有这样几种方式,forkspawnexecexecFile。spawn能够指定子进程执行的文件路径,也能够传递参数,可是API使用起来要麻烦一点,fork与spawn功能相似,在它上面又作了一次封装,因此在传递参数上面要更简洁一些,exec和execFile两个差很少,均可以直接执行命令或者传入可执行文件的路径。cluster是属于集群的API,能够更方便地处理主进程和其余子进程之间的关系,就是更容易地处理负载问题,由于Node的多进程都是属于一个Master进程管理多个Worker进程的形式。git

Electron中使用child_process模块

Electron中使用多进程有个坑,它不能在子进程中使用非Node标准模块的其余模块,好比第三方模块或者Electron中的模块,当你有这样的代码时就会出现错误,require('lodash')或者require('electron')。这是由于子进程中会有一个预设的环境变量,ELECTRON_RUN_AS_NODE=true,这样的话就会认为在Node的环境下执行,因此第三方模块和Electron是找不到的,并且这个值你是改不了的,能够参考这个issue,当你有这样的需求的时候就要考虑用一些其余的方式了。github

生成任务脚本

Electron在应用程序打包后会在一个asar文件中,里面的文件目录是不能直接去引用的,因此若是启动的任务是一个文件的执行路径,那么这个文件须要放在一个能够直接读取的路径上,那就须要在启动或者其余的某个时间去把工程中的脚本拷贝到磁盘某个能够正常访问的路径上。
能够采起这样的做法,把已经写好的脚本放在工程的static目录下,这样打包后就会原封不动地放在<应用目录>/dist/electron/static目录下,而后把对应的文件拷贝到指定路径,例如我这里直接拷贝到了应用目录的父级目录,这里说的应用目录就是你的asar文件所在目录。缓存

//copyUtils

const path = require('path');
const fs = require('fs');
import {app, dialog} from 'electron';

const FILE_NAME_LIST = ['clean.js'];

function copyFile(fileName, callback) {
    let fromPath = path.resolve(app.getAppPath(), 'dist', 'electron', 'static') + path.sep;
    let targetPath = path.resolve(app.getAppPath(), '../') + path.sep;
    let fromFileName = fromPath + fileName;
    let targetFileName = targetPath + fileName;
    if (!fs.existsSync(targetFileName)) {
        fs.readFile(fromFileName, (readErr, data) => {
            if (!readErr) {
                fs.writeFile(targetFileName, data, writeErr => {
                    if (writeErr) {
                        dialog.showErrorBox('WriteErr', writeErr.message);
                    } else {
                        callback(targetFileName);
                    }
                })
            } else {
                dialog.showErrorBox('ReadErr', readErr.message);
            }
        })
    } else {
        callback(targetFileName);
    }
}

export default {
    initScripts(callback) {
        if (process.env.NODE_ENV === 'production') {
            let allCount = DLL_NAME_LIST.length;
            let currentCount = 0;
            for (let item of DLL_NAME_LIST) {
                copyFile(item, name => {
                    ++currentCount;
                    if (currentCount === allCount) {
                        callback(true);
                    }
                });
            }
        }
    }
}

这个clean.js就是清理缓存的脚本。app

// clean.js

process.on('message', folder => {
    // 接收一个目录而后去查找匹配的文件并删除,具体的就不贴了,可有可无
    delete(folder);
    
    // 执行完自动退出
    process.exit(0)
});

console.log('clean task has created...');

当文件不存在的时候才会拷贝脚本,假如脚本存在可是已经被修改了,这时候是不知道的,那执行的时候就会出错,更好的作法是再校验一下文件的md5,若是文件损坏依然执行拷贝操做。electron

const crypto = require('crypto');
const fs = require('fs');

let hash = crypto.createHash('md5');
let buffer = fs.readFileSync('脚本目录');
hash.update(buffer);
let md5 = fsHash.digest('hex');
// 比较当前md5与预设的md5是否一致

部署脚本

能够选择在应用启动的时候执行文件的拷贝函数

// deployUtils

import {app} from 'electron'
import copyUtils from './copyUtils';
const path = require('path');
const cp = require('child_process');

const runtimeFolder = path.resolve(app.getAppPath(), '..') + path.sep;
const cleanProcessName = 'clean.js';

function startTask() {
    let cleanProcess = cp.fork(runtimeFolder + cleanProcessName);
    cleanProcess.send('清理的目录');
    // 有更多的任务能够一直继续fork追加
}

/**
 * 把全部.js结尾的文件都拷贝到指定目录
 * @param {String} srcDir 源文件目录 
 * @param {function} next 回调函数 
 */
function copyFile(srcDir, next) {
    fs.readdir(srcDir, (err, files) => {
        if (err) {
            console.log(err)
        } else {
            let index = 0;
            let targetCount = 0;
            for (let item of files) {
                if (item.endsWith('.js')) {
                    targetCount++;
                    let distFilePath = runtimeFolder + item;
                    // 同名文件会被覆盖
                    fs.copyFile(srcDir + path.sep + item, distFilePath, err => {
                        if (err) {
                            console.log(err)
                        } else {
                            index++;
                            if (index == targetCount) {
                                next();
                            }
                        }
                    })
                }
            }
        }
    })
}

export default {
    deploy() {
        if (process.env.NODE_ENV === 'production') {
            copyUtils.initScripts(() => {
                startTask();
            })
        } else {
            let fromDir = path.resolve(__dirname, '..', '..', 'static') + path.sep;
            new Promise(resolve => {
                copyFile(fromDir, resolve);
            }).then(() => {
                startTask();
            })
        }
    }
}

能够在建立窗口的时候启动ui

// src/main/index.js

import deployTask from './deployTask'

...

function createWindow() {
    deployTask.deploy();
}
相关文章
相关标签/搜索