【问题标题】:Strange line buffering behaviour in Tcl 8.6?Tcl 8.6 中奇怪的行缓冲行为?
【发布时间】:2018-12-07 00:14:02
【问题描述】:

编辑:为清晰起见,修改了原始示例和替代解决方案框架。

行缓冲行为可能与 Tcl 8.6 中的预期行为不同。以下代码块没有任何输出,除非“chan close”行未注释:

set data {one two four}
set stream [open |[list cat -n] r+]
chan configure $stream -buffering line

chan puts $stream "$data\n"
chan puts $stream "\n"
chan flush $stream
#chan close $stream write
set out [chan read $stream]
puts "output: $out"
chan close $stream

所以这个简单的解决方案不适用于交互式 I/O,这可能与管道两端的同步问题有关。

使用通道事件结构(例如,基于http://www.beedub.com/book/2nd/event.doc.html)似乎更可取:

proc chanReader { pipe } {

   global extState

   while 1 {
      set len [chan gets $pipe line]
      if { $len > 0 } {
         puts "<< $line."
         continue
      } else {
         if { [chan blocked $pipe] } {
            set extState 1
            return
         } elseif { [chan eof $pipe] } {
            set extState 2
            return
         }
      }
   }
}


set data {one two foure}
set timeout 5000

#set stream [open [list | cat -n] r+]
#set stream [open [list | ispell -a] r+]
set stream [open [list | tr a-z A-Z] r+]
#set stream [open [list | fmt -] r+]
chan configure $stream -blocking 0 -buffering line
set extState 0
chan event $stream readable [list chanReader $stream]

foreach word $data {
   puts "> $word\n"
   chan puts $stream "$word\n"
   chan flush $stream
   #chan close $stream write
   set aID [after $timeout {set extState 3}]
   vwait extState
   if { $extState == 1 } {
      # Got regular output.
      after cancel $aID
      puts "Cancel $aID."
      continue
   } elseif { $extState == 2 } {
      puts "External program closed."
      chan close $stream
      exit 2
   } elseif { $extState == 3 } {
      puts "Timeout."
      chan close $stream
      exit 3
   }
}

puts "End of task."
chan close $stream
exit 0

此代码片段适用于“cat -n”和“ispell -a”外部程序(注释行),但仍无法与其他外部程序一起使用。例如,它不适用于上面的“tr a-z A-Z”和“fmt”示例。

如果上面的“chan close $stream write”行没有注释,我们会收到来自外部程序的输出,但这会终止与它的交互。如何可靠地(交互地)连接到这些外部程序?

【问题讨论】:

  • 那么,为什么“chan close $stream write”命令被注释掉了?我想你一直在尝试,还有更多要告诉我们的。
  • 并且cat 命令将具有输入和输出缓冲。除非你能控制管道两端的行为,否则我认为什么都不能保证。
  • 是的,我想将此代码片段用于来自外部程序的连续读/写操作,并带有行缓冲通道(管道)。我不想一次写完所有东西,关闭频道,最后从外部程序中读取。我宁愿保持管道打开,并根据需要从中读取。因此我的问题
  • 您可以尝试在 8.6 上使用 chan pipe 看看是否有任何改变,但是 8.6 在这方面的语义应该与以前的版本相同(除了那些不能半关闭任何东西)。
  • 您可能应该注意到,在您的解决方案中,在 Reader proc 中您设置了一个局部变量“done”,但主执行线程正在一个 global 变量上等待“完毕”。在 Reader 中,您要么想要添加一行“global done”,要么将引用更改为“set ::done 1”以强制使用全局命名空间。

标签: tcl


【解决方案1】:

我猜这里的核心问题是有两个缓冲源在进行,而 Tcl 只能控制其中一个。但两者都源于这样一个事实:几乎所有输出,当不去“交互式”目的地(即终端)时,都会被缓冲。 C 标准库中基本上有一个调用来确定这一点并启用缓冲功能,并且 Tcl 也遵循该规则(尽管使用其完全独立的 I/O 库)。这样做大量加速了非交互式管道处理,但这意味着如果您希望在程序认为它正在编写它的时候准确地看到每个字节输出,那么您将失望。

当然,程序可以根据需要关闭此缓冲。在 Tcl 中,这是由fconfigure $channel -buffering none(或line 用于面向行的缓冲)完成的。在cat 中,-n 选项使其等效(在 C 中调用 setvbuf()),ispell 可能也在做同样的事情。但是大多数程序没有。有些人不时打电话给fflush();这也有效,但也是少数做法。因此,使用您正在使用的双向管道,您可以轻松地强制您从 Tcl 而非缓冲区馈入它的一侧,但您不能通常让另一侧做同样的事情。

有一个解决方法:使用 Expect 运行子进程。这在 Tcl 和子进程(而不是管道)之间放置了一个假终端,并欺骗它认为它正在直接与用户交谈。但是这样做的结果是您必须大量重写您的 Tcl 程序,并且您获得了对(非常好!)外部包的依赖。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多