当 Sublime 启动外部程序时(例如,当您运行构建并运行 python 时),它会启动两个后台线程来捕获正在运行的程序的 stdout 和 stderr 句柄的输出以便它可以显示在构建面板中。
Sublime 确保这两个线程中只有一个线程可以在任何时候将数据添加到输出面板,但是每个线程捕获的数据以任何方式都没有缓冲,因此只要任何数据出现在输出句柄上,它就会排队追加到输出面板。
您看到这种情况发生的原因是打印语句将输出发送到stdout,但异常写入stderr。由于多线程程序的性质,不确定两个线程中的哪一个可能首先捕获它的输出,这意味着两个句柄的输出以明显随机的方式混合。
我想 Sublime 是这样工作的,因为如果它默认缓冲输出到行,一次生成部分行的程序似乎根本没有运行。
在实践中,虽然很烦人,但这不应该对事情产生太大的有害影响,因为一般来说stdout 用于常规输出,stderr 用于错误消息,所以如果两者都需要使用,那么已经有一些不愉快的事情了正在发生。
还可以创建您自己的自定义构建 target,它模仿 exec 所做的,除了行缓冲,以防这样的事情是一个可靠的要求。
作为对上述内容的进一步解释(以及运行带有行缓冲的程序的示例),这里有一个示例 Python 程序,它更清楚地演示了这里发生的事情。在 10 秒内每秒生成一个 . 到 stdout 和一个 - 到 stderr,然后在 10 秒过去后,它会依次向每个句柄发送一个换行符。
import sys
import time
for _ in range(0,10):
sys.stdout.write(".")
sys.stderr.write("-")
sys.stdout.flush()
sys.stderr.flush()
time.sleep(1)
sys.stdout.write("\n")
sys.stderr.write("\n")
当我使用默认的Python.sublime-build 运行程序时,我会得到这样的结果:
.--.-..-.--.-.-.-.-.
[Finished in 10.1s]
这表明 stderr 和 stdout 的输出不仅混合在一起,而且由于捕获的多线程性质,它还以奇怪的不确定方式进行混合。
例如,您可能希望输出以交替序列的形式出现,例如 .-.-.-.-.-.-.-.-.-.-,但事实并非如此;尽管每个项目有 10 个,但它们出现的顺序与我们打印出来的顺序不同,因为每个线程被安排运行的顺序并注意到要添加到面板的输出是不确定的。
正如我上面提到的,您可以创建自己的自定义构建目标,它执行 exec 命令所做的工作,除了将数据保存在中间缓冲区中并且一次只将其释放到输出面板行而不是作为它出现了,这将阻止这种情况发生。
重要提示:像这样进行行缓冲不会阻止来自stderr 和stdout 的数据交错,它只是确保数据整个交错一次行,而不是把所有的“外星人胸膛爆裂器”都放在彼此的中线上。要真正停止交错,您需要将每个句柄发送到不同的位置(例如两个面板或将stderr 发送到控制台)。
此类内容的完整代码太长,无法添加到 SO 答案中,因此出于演示目的,我将代码放在 this gist 中。修订版 1 是文件的基础版本,修订版 2 是用于缓冲的修改版本,因此您可以查看 the differences between the two 以更好地了解更改。
默认的exec 命令捕获来自stderr 和stdout 的输出,并在收到后直接发送到输出面板;当数据到达输出面板时,命令会将行尾字符标准化为 Sublime 期望看到的(对于 Windows/Mac 计算机),然后将其添加到面板中。
修改后的版本将数据读入临时缓冲区并立即进行换行规范化,然后仅沿其累积的任何整行转发,将其余数据留给下一次使用。输出完成后,所有剩余的数据都会发送到缓冲区,以便显示所有内容。
为了使用新的目标,sublime-build 文件必须添加一个target 参数来告诉Sublime 使用exec 以外的命令。 Gist 有一个默认 Python.sublime-build 的示例,以及一个要添加的额外键以允许 Cancel Build 功能与自定义目标一起使用。
修改后的构建就位后,程序的输出变为:
----------
..........
[Finished in 10.1s]
请注意,这样做的副作用是,现在您在 10 秒内在构建面板中什么都看不到,然后所有的输出都立即出现,因为在完成之前它不会出现;这种事情可能是 Sublime 默认不这样做的原因。
如上所述,这不会阻止数据交织,它只是控制数据的发生方式,因此一次发生一行。仍然不确定stdout 和stderr 中哪一行先出现。
如果你愿意,你可以修改从第 169 行开始的代码:
# Accumulate data into our buffer and only let full lines
# through to the output panel.
if self.listener:
pBuffer += data
nPos = pBuffer.rfind("\n")
if nPos >= 0:
self.listener.on_data(self, pBuffer[:nPos+1])
pBuffer = pBuffer[nPos+1:]
到这里:
# Accumulate data into our buffer and only let full lines
# through to the output panel; stderr data is held until the
# handle is closed below.
if self.listener:
pBuffer += data
if execute_finished:
nPos = pBuffer.rfind("\n")
if nPos >= 0:
self.listener.on_data(self, pBuffer[:nPos+1])
pBuffer = pBuffer[nPos+1:]
这会修改一些东西,以便stderr 的数据存储在缓冲区中,但直到程序终止后才释放到输出面板。
这样做可以确保没有交错,但也会阻止您在错误发生时看到错误。如果您的程序长时间运行,也无法看到发生的错误,并且如果有大量错误输出被缓冲但未显示,则可能导致内存使用量增加。
因此,我不建议这样做,除非您正在执行诸如运行短期程序之类的操作,这样在最后出现错误的干扰较小。