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();
}
相關文章
相關標籤/搜索