You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Electron应用长耗时挖矿函数执行时DOM无法实时更新问题

解决Electron挖矿函数阻塞UI、无法实时更新nonce的问题

这个问题我太熟了——本质是JavaScript单线程模型和Electron的进程分工导致的:你的挖矿函数是同步的密集计算任务,完全占用了渲染进程的主线程,Chromium根本没机会处理UI更新、用户点击这些事件,直到挖矿结束才会一次性把所有pending的DOM更新刷出来,所以页面会冻结,最后才看到结果。

下面给你两种落地性很强的解决方案,选哪种看你的需求:

方案1:用Web Workers把计算移到后台线程

适合纯前端计算的场景,不需要主进程介入,代码改动相对小。

步骤1:把挖矿逻辑抽成Worker文件

新建一个miner.worker.js文件,把挖矿的核心逻辑放进去,通过消息和渲染进程通信:

const crypto = require('crypto');

// 监听渲染进程发来的挖矿请求
self.onmessage = (e) => {
  const { index, time, prev, transactions, difficulty } = e.data;
  
  if (index !== 0) {
    self.postMessage({ type: 'log', message: `Mining Block ${index}` });
  }

  const startTime = Date.now();
  let nonce = 0;
  let hash = '';

  while (hash.substring(0, difficulty) !== Array(difficulty + 1).join('0')) {
    nonce++;
    hash = crypto
      .createHash('sha256')
      .update(
        index.toString() + time.toString() + prev.toString() + transactions.toString() + nonce.toString()
      )
      .digest('hex')
      .toString();

    // 别每次nonce++都发消息,太耗性能!每隔1000次更一次
    if (nonce % 1000 === 0) {
      self.postMessage({ type: 'nonce', value: nonce });
    }
  }

  // 最后发一次最终的nonce值
  self.postMessage({ type: 'nonce', value: nonce });

  // 计算性能和难度调整
  const performanceTime = Date.now() - startTime;
  if (performanceTime <= 60000 && index !== 0) {
    self.postMessage({ type: 'difficulty', value: difficulty + 1 });
    self.postMessage({ type: 'log', message: `Difficulty Increased. Difficulty Is Now ${difficulty + 1}` });
  }

  const seconds = performanceTime / 1000;
  const performance = Math.floor((10 * nonce) / seconds) / 10000;
  let performanceText = 'Error';

  if (index !== 0) {
    if (performance !== Infinity && performance >= 25) {
      performanceText = `${performance} kh/s`;
      self.postMessage({ type: 'log', message: `Performance: ${performance} kh/s` });
    } else {
      self.postMessage({ type: 'log', message: `Performance Could Not Be Measured` });
    }
    self.postMessage({ type: 'log', message: `Block ${index} Successfully Mined` });
  }

  self.postMessage({ type: 'performance', value: performanceText });
  self.postMessage({ type: 'done', nonce });
};

步骤2:在渲染进程里调用Worker

修改你原来的渲染进程代码,创建Worker并监听消息更新UI:

let minerWorker = null;

function mineHandler(props) {
  // 防止重复点击启动多个挖矿任务
  if (minerWorker) {
    minerWorker.terminate();
  }

  minerWorker = new Worker('./miner.worker.js');

  // 接收Worker发来的各种更新
  minerWorker.onmessage = (e) => {
    const { type, value, message, nonce } = e.data;
    switch(type) {
      case 'nonce':
        $("#nonce").text(`Nonce: ${value}`);
        break;
      case 'performance':
        $("#performance").text(`Performance: ${value}`);
        break;
      case 'log':
        this.log(message); // 假设你的日志函数是this.log
        break;
      case 'difficulty':
        this.difficulty = value;
        break;
      case 'done':
        // 挖矿结束,清理Worker
        minerWorker.terminate();
        minerWorker = null;
        break;
    }
  };

  // 把挖矿参数传给Worker
  minerWorker.postMessage(props);
}

$("#miningBtn").click(mineHandler);

方案2:用Electron主进程处理挖矿任务

如果你的挖矿逻辑需要访问Node.js深层API或者系统资源,用主进程更合适——主进程是独立线程,完全不会阻塞渲染进程的UI。

步骤1:在主进程里添加IPC监听

修改你的main.js(主进程入口文件),添加挖矿的IPC处理:

const { ipcMain } = require('electron');
const crypto = require('crypto');

// 监听渲染进程的挖矿请求
ipcMain.handle('start-mining', async (event, props) => {
  const { index, time, prev, transactions, difficulty } = props;
  
  if (index !== 0) {
    // 给渲染进程发日志消息
    event.sender.send('mining-log', `Mining Block ${index}`);
  }

  const startTime = Date.now();
  let nonce = 0;
  let hash = '';

  while (hash.substring(0, difficulty) !== Array(difficulty + 1).join('0')) {
    nonce++;
    hash = crypto
      .createHash('sha256')
      .update(
        index.toString() + time.toString() + prev.toString() + transactions.toString() + nonce.toString()
      )
      .digest('hex')
      .toString();

    // 每隔1000次更新一次nonce
    if (nonce % 1000 === 0) {
      event.sender.send('mining-nonce-update', nonce);
    }
  }

  event.sender.send('mining-nonce-update', nonce);

  // 计算性能和难度调整
  const performanceTime = Date.now() - startTime;
  let newDifficulty = difficulty;
  if (performanceTime <= 60000 && index !== 0) {
    newDifficulty = difficulty + 1;
    event.sender.send('mining-log', `Difficulty Increased. Difficulty Is Now ${newDifficulty}`);
  }

  const seconds = performanceTime / 1000;
  const performance = Math.floor((10 * nonce) / seconds) / 10000;
  let performanceText = 'Error';

  if (index !== 0) {
    if (performance !== Infinity && performance >= 25) {
      performanceText = `${performance} kh/s`;
      event.sender.send('mining-log', `Performance: ${performance} kh/s`);
    } else {
      event.sender.send('mining-log', `Performance Could Not Be Measured`);
    }
    event.sender.send('mining-log', `Block ${index} Successfully Mined`);
  }

  event.sender.send('mining-performance-update', performanceText);

  // 返回最终结果给渲染进程
  return { nonce, newDifficulty };
});

步骤2:在渲染进程里通过IPC调用主进程

修改渲染进程的代码,用ipcRenderer和主进程通信:

const { ipcRenderer } = require('electron');

let isMining = false;

function mineHandler(props) {
  if (isMining) return;
  isMining = true;

  // 定义各个更新的监听函数
  const handleNonceUpdate = (event, nonce) => {
    $("#nonce").text(`Nonce: ${nonce}`);
  };

  const handleLog = (event, message) => {
    this.log(message);
  };

  const handlePerformanceUpdate = (event, text) => {
    $("#performance").text(`Performance: ${text}`);
  };

  // 注册监听
  ipcRenderer.on('mining-nonce-update', handleNonceUpdate);
  ipcRenderer.on('mining-log', handleLog);
  ipcRenderer.on('mining-performance-update', handlePerformanceUpdate);

  // 调用主进程的挖矿任务
  ipcRenderer.invoke('start-mining', props)
    .then(({ nonce, newDifficulty }) => {
      this.difficulty = newDifficulty;
      // 清理监听,避免内存泄漏
      ipcRenderer.removeListener('mining-nonce-update', handleNonceUpdate);
      ipcRenderer.removeListener('mining-log', handleLog);
      ipcRenderer.removeListener('mining-performance-update', handlePerformanceUpdate);
      isMining = false;
    })
    .catch((err) => {
      console.error('Mining failed:', err);
      // 出错也要清理监听
      ipcRenderer.removeListener('mining-nonce-update', handleNonceUpdate);
      ipcRenderer.removeListener('mining-log', handleLog);
      ipcRenderer.removeListener('mining-performance-update', handlePerformanceUpdate);
      isMining = false;
    });
}

$("#miningBtn").click(mineHandler);

关键注意事项

  • 不要频繁发送消息:不管用Worker还是IPC,别每次nonce++都发更新,这样会把通信开销拉满,反而拖慢挖矿速度。我上面用的是每1000次发一次,你可以根据自己的需求调整这个数值。
  • 避免重复任务:一定要加判断,防止用户多次点击按钮启动多个挖矿进程,浪费资源。
  • 清理资源:Worker用完要terminate,IPC监听用完要removeListener,避免内存泄漏。

内容的提问来源于stack exchange,提问作者L. Rothman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:40:04