Jupyter Notebook 消息通信的阻塞问题

Jupyter Notebook使用ws进行python层面的输入输出交互,业务上使用自己实现的客户端,遇到了一个问题:ws接收消息的时候不一定先发先到,由于网络波动问题可能出现顺序错乱,此时如果input指令先于print指令到达,会产生阻塞,导致print无法正常处理。记录一下解决过程。

问题还原

Notebook kernel发送过来的message简化为两种:文字输出和“要求输入”,即input。对于如下代码:

print("请输入您的姓名")
name = input()

客户端先后收到这两条消息

  1. 要求输入,时间为14:23:23.421
  2. 输出“请输入您的姓名”,时间为14:23:23.233

即接收到的message顺序和代码实际运行的顺序不对,但是@jupyterlab/services包并没有对message进行重新排序,无法保证处理顺序和实际的顺序一致。并且对于message的处理,采用线性await的形式,即等待前一条message的promise处理完毕,才进行下一条。

而业务中,对于input,使用了一个promise等待用户输入,用户输入之后才resolve。因此,这个场景下,会出现需要先输入姓名,再显示出“请输入您的姓名”。

解决思路

首先尝试解决乱序问题

第一反应就是在客户端保证处理的时候按照时间顺序来,因为每一条message都有标记发生的时间。大致的解决方案为:建立一个缓冲区,在缓冲时间断内,先不处理消息,而是等待一段时间后进行排序。例如,设置一个400ms的缓冲区,一个消息A到达后,等待400ms,如果期间有新的消息B进入,并且时间戳早于A,则需要排队,A需要在B执行完成后再执行。

这个方案需要修改@jupyterlab/services sdk代码,方案本身也较为复杂,并且无论缓冲时间段设置多短,都会造成比较明显的延迟,体验上大打折扣。

其实只需提前resolve

由于上面的方案过于复杂,拖延了一段时间没有进行。之后突然想到,其实根本没有那么复杂,虽然sdk里的代码会对每一条消息的处理await,表面上看起来好像是必须前一条处理完才能处理下一条,所以用户还没有输入完input就不能展示print,但其实可以提前resolve… 原来的代码为:

async function resolveMessage(message) {
  if (message.type === 'stdin') {
    const name = await userInput();
    sendMessage({ name });
  }
  ...
}

修改为:

async function resolveMessage(message) {
  if (message.type === 'stdin') {
    userInput().then(name => sendMessage({ name });
  }
  ...
}

问题解决。

经验就是,写代码也是需要灵感的,搞不定就先打两把游戏,说不定就豁然开朗了

文章原始链接:https://sijie.wang/posts/jupyter-notebook-message-promise

本站文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请保留原始链接