美文网首页
electron多进程方案解决界面卡顿

electron多进程方案解决界面卡顿

作者: 剑老师 | 来源:发表于2021-08-22 22:11 被阅读0次

    本文首发于公众号【一个老码农】

    因公司业务需要,最近参与了一个公司的桌面应用。技术栈为electron+react,界面部分主要用的是antd

    在开发过程中遇到一个问题:有一个非常耗时的任务,当任务开启时,会导致整个应用界面卡死,只能等待任务执行结束才能进行界面操作。今天就来讲一下在electron中密集型任务的处理方法。

    卡顿原因

    由于electron应用使用的是js作为开发语言,而js是单线程的,所以electron也是没有多线程的,但是它却允许有多进程。在electron中分为主进程和渲染进程,主进程在main文件中进行控制,同时这个文件还负责控制应用的生命周期。(注:main文件在package.json配置文件中的main字段中进行配置)而每一个窗口则是一个渲染进程即我们所说的UI进程。经过研究发现,应用界面卡死是由于任务比较耗资源,进而造成当前渲染进程卡死。

    怎样避免UI卡顿

    关于electron应用的性能优化,electron的官方文档给出了以下几点建议:
    每当Node.js的核心模块 (如fs) 提供一个同步版本或异步版本,你更应该使用异步和非阻塞式的变量

    尽可能避免使用同步IPCremote 模块,使用remote模块的时候非常容易不知情地阻塞 UI线程

    对于需要长期占用CPU的繁重任务,可以将它们移动到 BrowserWindow, 生成一个专用进程。(也就是新建一个窗口)

    具体解决方案

    基于官方文档,我采用了使用新建一个隐藏的BrowserWindow的方式,把耗时任务放入新的窗口中。由于每一个BrowserWindow都是一个单独的渲染进程,所以此方法可以避免界面卡顿。而任务处理进度,隐藏的渲染进程(后用"任务进程"代替)可以通过IPC通信的方式告诉主进程,主进程再通知当前渲染进程。
    为了方便起见也可以在当前渲染进程中用remote.ipcMain.on()来与任务进程通信。

    核心代码如下:

    在当前渲染进程中监听,由于渲染进程与渲染进程之间不能通信,所以可以用remote.ipcMain的方式拿到主进程对象进行监听

    //当前渲染进程
    import { remote } from 'electron'
    import React, { useState, useEffect, useRef } from 'react'
    
    
    const win = useRef()
    
    useEffect(() => {
        return () => {
          closeArchiveRenderer()
        }
      }, [])
    
    //开始任务
    function begain() {
        listenIpcRenderer()
        createNewWindow()
    }
    
    //创建隐藏window
    function createNewWindow() {
        let win.current = new remote.BrowserWindow({
          width: 500,
          height: 500,
          show: false,
          webPreferences: {
            nodeIntegration: true,
            contextIsolation: false,
            enableRemoteModule: true,
          },
        })
        win.current.loadURL('xxxxxx')
    }
    
    //ipc通信监听
    function listenIpcRenderer() {
        remote.ipcMain.on('window-load-success', (event, arg) => {
          console.log(arg)
          if (arg) {
            event.reply('begain-task', {
                //tag标记任务类型
              tag: 'xxx', 
              //传给任务进程的数据
              dataSource: dataSource
            })
          }
        })
    
    remote.ipcMain.on('change-task-status', (event, arg) => {
          console.log(arg)
          if (arg.error) {
            console.log(arg.error)
            return
          }
          if (arg.status === 2) {
            //状态处理
          }
        })
    
    remote.ipcMain.on('task-complete', (event, arg) => {
        //任务完成
        //关闭进程,移除监听,可根据需要放在合适的地方
        closeArchiveRenderer()
        })
    }
    
    
    //要在合适的时机把进程关闭,并移除监听
    function closeArchiveRenderer() {
        if (win.current) {
          win.current.close()
          win.current = null
        }
        remote.ipcMain.removeAllListeners()
      }
    
    

    任务专用进程中需要这样处理

    // 任务进程,当前窗口处理隐藏状态
    import { ipcRenderer } from 'electron'
    
    useEffect(() => {
        ipcRenderer.on('begain-task', (event, arg) => {
          // 根据tag判断任务类型
          if (arg.tag === 'xxxx') {
            console.log(arg.dataSource)
            //任务处理TODO
            xxxx()
            //以下代码可根据需要放在合适的位置
            //如果处理状态有变化则发送变化通知
            ipcRenderer.send('change-task-status', data)
    
            //如果任务处理完成,则发送完成通知
            ipcRenderer.send('task-complete', data)
          }
        })
    
        ipcRenderer.send('window-load-success', true)
        return () => {
          ipcRenderer.removeAllListeners()
        }
      }, [])
    

    关注公众号【一个老码农】,更多优质技术内容等你来
    原文地址

    相关文章

      网友评论

          本文标题:electron多进程方案解决界面卡顿

          本文链接:https://www.haomeiwen.com/subject/uaghiltx.html