Tcl 在8.4中运行的代码导致了8.6中的崩溃,是否有更好的方法实现该功能
我有一个Tcl实用程序,可以轻松确保在控制流离开过程的当前范围时运行代码片段。它在TCL8.6.6中崩溃,所以我想知道是否有更好的方法来实现TCL8.6中的功能 一个示例用法是:Tcl 在8.4中运行的代码导致了8.6中的崩溃,是否有更好的方法实现该功能,tcl,Tcl,我有一个Tcl实用程序,可以轻松确保在控制流离开过程的当前范围时运行代码片段。它在TCL8.6.6中崩溃,所以我想知道是否有更好的方法来实现TCL8.6中的功能 一个示例用法是: proc test {file} { set fh [open $file] ::Util::Defer [list close $fh] # ... do a bunch of stuff # and even if we hit an error # [close $fh] will
proc test {file} {
set fh [open $file]
::Util::Defer [list close $fh]
# ... do a bunch of stuff
# and even if we hit an error
# [close $fh] will be evaluated as we return
# from the proc
}
它在TCL8.4中工作得非常好,我在代码中都使用它
由于我仍在学习Tcl 8.6中的所有可用功能,我想问的是,应该如何编写::Util::Defer proc以最好地利用Tcl 8.6
以下是8.4的实现:
namespace eval ::Util {}
proc ::Util::Defer_impl {cmd args} {
uplevel 1 $cmd
}
proc ::Util::Defer {cmd} {
set vname _u_defer_var
# look for a unique variable name
while {[uplevel 1 [list info vars $vname]] != ""} {
set vname ${vname}_
}
uplevel 1 [list set $vname $cmd]
# when the variable is unset, trigger a call to the command
uplevel 1 [list trace add variable $vname unset [list ::Util::Defer_impl $cmd]]
# return a chunk of code enabling the user to cancel this if desired
return [list variable $vname unset [list ::Util::Defer_impl $cmd]]
}
编辑以添加:
我很感激你的回答。老实说,我已经为文件句柄提供了其他语法糖,如下所示:
proc test {file} {
set fh [::Util::LocalFileHandle $file]
# do stuff
}
我只是希望得到一个针对::Util::Defer的通用解决方案——因为我偶尔会在同一个proc中的不同位置使用两到三个。是的,如果不存在或不可读,我将省略错误处理
注意:我已经向ActiveState报告了这个bug,并提交了一份报告
编辑以添加错误代码:这是导致我崩溃的Tcl代码,它稍微精简到了本质,而不是完整的::Util::Defer
未经测试的代码正是这段代码,我已经多次使用此模式:
proc test file {
try {
open $file
} on ok fh {
# do stuff with fh
# more stuff
} finally {
catch {close $fh}
}
}
应该差不多。无论是否使用try结构处理错误,或者是否出现错误,finally子句中的代码在结束时都会运行。如果希望能够取消操作,请在子句中使用简单的If
编辑
如果希望看到通道关闭时生成的任何错误,最好将其包装在catch中,如果无法打开文件且未创建通道id变量,则必须使用catch。备选方案包括:
检查是否存在:如果{[info exists fh]}{close$fh}
传播关闭错误:使用result和options变量名参数捕获。
您描述的模式是受支持的模式;它不应该崩溃,事实上,我不能用8.6.3或8.6支持分支的尖端重现崩溃。它唯一的问题是,如果在关闭或任何其他延迟脚本期间出现错误,它将不会报告错误,正如您从该代码段中看到的那样%is提示符:
% apply {{} {
::Util::Defer [list error boo]
puts hi
}}
hi
%
这就是为什么我花了很多精力在8.6中提供try命令的部分原因。有了它,您可以执行以下操作:
proc test {filename} {
set f [open $filename]
try {
# Do stuff with $f
} finally {
close $f
}
}
它还处理一些棘手的事情,比如将正文和finally子句中抛出的错误缝合在一起。正文异常信息位于finally子句的error exception info的-during选项中,这样,如果两个地方都有错误,您就可以同时找到这两个地方
% catch {
try {
error a
} finally {
error b
}
} x y
1
% puts $x
b
% puts $y
-errorstack {INNER {returnImm b {}}} -errorcode NONE -errorinfo {b
while executing
"error b"} -errorline 5 -during {-code 1 -level 0 -errorstack {INNER {returnImm a {}}} -errorcode NONE -errorinfo {a
while executing
"error a"} -errorline 3} -code 1 -level 0
就我个人而言,我更倾向于这样写:
proc withreadfile {varName filename body} {
upvar 1 $varName f
set f [open $filename]
try {
return [uplevel 1 $body]
} finally {
close $f
}
}
proc test {file} {
withreadfile fh $file {
# Do stuff with $fh
}
}
您的里程数可能会有所不同。周末,这个重量级解决方案浮现在脑海中。它利用itcl::local功能实现相同的效果。它确实依赖于Itcl,但由于问题是与Itcl的交互,因此这似乎是一个合理的解决方案,尽管它不是纯粹的Tcl
itcl::class Defer_impl {
constructor {cmd} {} {
set _to_eval $cmd
}
destructor {
uplevel 1 $_to_eval
}
private variable _to_eval {}
}
proc ::Util::Defer {cmd} {
uplevel 1 [list itcl::local ::Defer_impl #auto $cmd]
}
在Ubuntu16.04下,TCL8.6.0似乎没有这样的问题。我没有8.6.0,只有8.6.4和8.6.6,对于Red Hat 6.6.Hmm,我会使用set vname[expr rand]作为延迟变量的初始猜测。如果打开失败,代码将尝试关闭未设置的变量。您可以通过捕获来处理这一问题,这将丢失关闭管道时的错误…罪名成立,大人。谢谢,我喜欢新的try/finally,并将开始使用它,但我希望能够有一点替代::Util::Defer的语法糖。这可能是我们得到的构建中的一个bug,或者是某个环境中的bug,或者。。。我不知道。我可能需要自己构建Tcl/Tk/IncrTcl,看看是否还能复制这个。使用提供的调试库和优化后的脚本,我肯定会得到一个空指针和一个seg错误…@TreyJackson IncrTcl?这可能与问题有关。它将触角深深地伸入Tcl,可能会让事情陷入一种奇怪的状态。你所做的应该是有效的;它至少支持到8.*系列结束。我为9.0计划了一些不同的东西——更多地关注堆栈框架消失的用例——因为局部变量跟踪对于高级编译来说是有问题的。我相信它与IncrTcl密切相关,因为它只在我从IncrTcl类中的proc而不是方法中使用时失败。我将更新我的答案,以获得错误报告中的代码,从而明确说明什么不起作用。这是一个增量错误,dgp已在4.0.6中修复了它。
itcl::class Defer_impl {
constructor {cmd} {} {
set _to_eval $cmd
}
destructor {
uplevel 1 $_to_eval
}
private variable _to_eval {}
}
proc ::Util::Defer {cmd} {
uplevel 1 [list itcl::local ::Defer_impl #auto $cmd]
}