electron渲染线程与主线程通信,渲染线程与渲染线程通信

线程通信接口

在electron中,我们常常需要进行主线程与渲染线程,渲染线程与渲染线程的通信,这些方法有同步或者异步方式,让我们通过官方提供的一些函数来具体了解


同步信息通信时,由渲染线程在ipcRenderer注册发送与接受事件(sync-send-event,sync-receive-event),并封装成方法=>(sendSync 以及recieveSyncMsg )。下一步,将封装的方法抛出到contextBridge.exposeInMainWorld。抛出形式为一个键值对
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LA6vzVRP-1692336887703)(/uploads/5505adede678c9501ebdf30d0cde4011/image.png)]
其中键相当于是一个实例化对象,可以调用它的值的方法,当它的值是一组数据是,也可以直接读取。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tl2EMqDZ-1692336887704)(/uploads/16ff935f03bf7614981393a5f63753e9/image.png)]
当上述都做完的时候,我们要建立主线程和渲染线程的同步事件监听。此时监听的对象就是我们第一步注册的发送与接收事件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5qSXgIi0-1692336887705)(/uploads/ba61c6d85ff5158ab6213c998f565bc9/image.png)]
这里的event就是ipcMain,event.sender.send也就是ipcMain的方法,它的作用就是直接从主线程发送消息到渲染线程

上面是面向例子的解释,太过片面,下面基于官方api来解释一波

1、主线程与渲染线程之间通信,同步、异步:

渲染线程向主线程发送消息主要是有三个函数可以选择:

ipcRenderer.send(channel, ...args)
ipcRenderer.invoke(channel, ...args)// 适合异步
ipcRenderer.sendSync(channel, ...args)//适合同步

(1)「ipcRenderer.send(channel, …args)」:

它最简单明了,发送消息和监听主线程回复是分开的:

function sendMessageToMain() {
  ipcRenderer.send('render-send-to-main', '我是渲染进程通过 send 发送的消息');
}
ipcRenderer.on('main-reply-to-render', (event, message) => {
  console.log('replyMessage', message); // 'replyMessage 主进程通过 reply 回复给渲染进程的消息'
})

//当主线程想要回复时(也可以不回复),并不是在监听发送事件的回调函数中return,而是调用reply接口:

ipcMain.on('render-send-to-main', (event, message) => {
  console.log(`receive message from render: ${message}`)
  event.reply('main-reply-to-render', '主进程通过 reply 回复给渲染进程的消息')
})

(2)「ipcRenderer.sendSync(channel, …args)」:

和上面不一样的是,它的通信事件封装成了event,主线程接收到后,event.returnValue便是它的回复消息,这个回复消息反过来又会变成渲染线程那边的方法的返回值:

// render.js
const { ipcRenderer } = require('electron');

function sendSyncMessageToMain() {
  const replyMessage = ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程通过 syncSend 发送给主进程的消息');
  console.log('replyMessage', replyMessage); // '主进程回复的消息'
}
// main.js
const { ipcMain } = require('electron');

ipcMain.on('render-send-sync-to-main', (event, message) => {
  console.log(`receive message from render: ${message}`)
  event.returnValue = '主进程回复的消息';
})

由于它的这个特性,使这个方法天生适合同步消息,异步也不是不行,但是如果未及时指定返回值,是None的话会出现问题。

(3)「ipcRenderer.invoke(channel, …args)」

它的主线程回复消息也是发送事件方法的返回值,在主线程中直接return回复消息即可,但是它更适合异步消息,因为这个方法下,主线程回复消息与否都没关系,invoke的返回值永远是一个 Promise,而且用不用await都是一样的

// render.js
const { ipcRenderer } = require('electron');

async function invokeMessageToMain() {
  const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息');
  console.log('replyMessage', replyMessage);
}
// main.js
const { ipcMain } = require('electron');

ipcMain.handle('render-invoke-to-main', async (event, message) => {
  console.log(`receive message from render: ${message}`)
  const result = await asyncWork();
  return result;
})

const asyncWork = async () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('延迟 2 秒获取到主进程的返回结果')
    }, 2000)
  })
}

2、渲染线程与渲染线程:

ipcRenderer.sendTo(webContentsId, channel, …args) : 使用 ipcRenderer 提供的 sendTo 方法,指定要给哪个渲染进程(webContentsId)发送消息;

electron渲染线程与主线程通信,渲染线程与渲染线程通信_第1张图片

window.webContents.send :主进程保存所有渲染进程的 webContents 对象,同时主进程拥有接收渲染进程消息的能力,那么主进程就可以充当中间人的角色,使渲染进程之间能够通信;

  1. 每次新产生渲染进程的时候,都将渲染进程的 window.webContents 对象保存在主进程中;

  2. 渲染进程一想要给渲染进程二发消息的时候,就通知主进程,同时附带上事件名(消息名)和参数(消息内容);

  3. 主进程找到渲染进程二的 window.webContents ,通过它的 send 方法给渲染进程二自己发送消息;

  4. 渲染进程二那边用 ipcRenderer.on 来接收即可。

3、Messageport

生成port:

const channel = new MessageChannel();
const port1 = channel.port1
const port2 = channel.port2

// 或者简写为:

const { port1, port2 } = new MessageChannel();

简单可以理解为,我们有一批内部通信的对讲机,用的同一个频段(channel),以前的渲染进程之间的通信要经过主线程,相当于bb机,多了以后主线程会很忙,用port的话相当于对讲机,不用寻呼台就可以直接通信,但是问题在于一个,就是渲染线程发送port的接收人只能是主线程,这个意思就是我们一个渲染线程造了一批对讲机(port),要把它们给主线程,主线程再去分发给其他渲染线程,然后才可以开始脱离主线程进行通信。

具体接口是 **portMessage **

它的参数如下:

ipcRenderer.postMessage(channle, message, [transfer])

channel String:事件名

message any:要传递的消息

transfer MessagePort[] (optional):0个或多个 MessagePort 对象。

4、渲染线程和渲染线程通信实例

虽然理论上我们已经知道了渲染线程如何通过主线程进行通信,但是我们还有一些细节需要了解,比如,主线程拔剑四顾心茫然,不知道要通信的渲染线程是哪个(之前的方法都是渲染线程直接通过事件接口向主线程通信的),要解决这个问题,我们要了解渲染线程的id

渲染线程渲染后会在主线程有一个id,如果渲染线程没有关闭,那id大概率不会变,所以我们的思路就是渲染线程在渲染时向主线程发送一个消息事件,消息是自己的名字,主线程查看它的id,讲名字-id键值对存在主线程这里,以后就可以通过名字来知道线程id,从而进行通信了,就算重新渲染了也不用怕,还会再在键值对处更新的

  • 渲染线程(前端)全局存放处的接口
interface Render2RenderMsg {
  fromName: any
  toName: any
  msg: any
}

export const ComStore = defineStore("Com", () => {
  const sendSyncMessageToMain = (eventName, message) => {
    const replyMessage = ipcRenderer.sendSync(eventName, message)
    // console.log('replyMessage', replyMessage); // '主进程回复的消息'
    return replyMessage
  }

  //通过事件向主线程发送消息,让主线程转发给相应的id的渲染线程
  const sendRender2Render = (fromName, toName, msg) => {
    const render2renderMsg: Render2RenderMsg = {
      fromName: fromName,
      toName: toName,
      msg: msg
    }
    const reply = sendSyncMessageToMain("RenderNeedTransmit", render2renderMsg)
    return reply
  }

  //通过事件向主线程请求自己的id,并由主线程记录自己的名字id键值对。
  const requestpageId = (pagename) => {
    // const targetWindow = BrowserWindow.getFocusedWindow();
    // console.log(pagename,"窗口id为:",targetWindow?.id)
    const eventname = pagename + "sendRenderPageId"
    const pageid = sendSyncMessageToMain(eventname, pagename)
    console.log(pagename + "窗口id为:", pageid)
    return pageid
  }

  //渲染线程专门监听来自别的渲染线程经由主线程转发给自己的消息的事件
  //默认事件名称为pagename + "revFromRender"
  const revFromRender = (pagename: string) => {
    const eventname = pagename + "revFromRender"
    console.log("eventName1: ", eventname)
    ipcRenderer.on(eventname, (data) => {
      // 在这里处理接收到的消息
      console.log("接收到的消息:", data)
      return data
    })
  }

  return {
    sendSyncMessageToMain,
    sendRender2Render,
    requestpageId,
    revFromRender
  }
})
  • 后端的接口
interface pageId {
  pagename: string
  pageid: any
}

interface RenderNameIdDic {
  [key: string]: any
}

const renderNameIdDic: RenderNameIdDic = {}

//主线程收到需要转发的消息后根据id进行转发
export function transmitRender2RenderMsg() {
  ipcMain.on("RenderNeedTransmit", (event, Render2RenderMsg) => {
    const targetWindowId = renderNameIdDic[Render2RenderMsg.toName]
    const targetWindow = BrowserWindow.fromId(targetWindowId)
    const eventname = Render2RenderMsg.toName + "revFromRender"
    console.log("eventName: ", eventname)
    if (targetWindow) {
      targetWindow.webContents.send(eventname, Render2RenderMsg)
      event.returnValue = "msg has sent from [" + Render2RenderMsg.fromName + "] to [" + Render2RenderMsg.toName + "]"
    } else {
      event.returnValue = "page is null"
      return
    }
  })
}

function handleId(event, message) {
  console.log("sender:", event.sender)
  const targetWindow = event.sender.id // 或通过其他方式获取目标窗口
  console.log("targetWindow.id:", targetWindow)
  if (!targetWindow) {
    console.log("page is null")
    return
  }
  const pageid: pageId = {
    pagename: message,
    pageid: targetWindow
  }
  renderNameIdDic[pageid.pagename] = pageid.pageid
  event.returnValue = renderNameIdDic
}

//主线程收到渲染线程的id请求事件,分配id后,将name,id键值对存入字典。
export function handleRenderId() {
  ipcMain.on("BasicEditorsendRenderPageId", (event, message) => {
    handleId(event, message)
  })

  ipcMain.on("FooterbarsendRenderPageId", (event, message) => {
    handleId(event, message)
  })
}

你可能感兴趣的:(electron,javascript,前端)