如何从tcltk脚本小部件成功中断R?

Tho*_*mas 3 user-interface tk-toolkit r tcl

我正在尝试使用该tcltk包为R创建脚本小部件.但我不知道如何创建一个STOP按钮来中断来自小部件的脚本.基本上,我想有一个按钮,一个菜单选项,和/或一个会中断当前脚本执行的键绑定,但我无法弄清楚如何让它工作.

一个(非理想的)策略是只使用RGui STOP按钮(<ESC><Ctrl-c>在控制台上),但这似乎会导致tk小部件永久挂起.

这是基于tcl/tk示例(http://bioinf.wehi.edu.au/~wettenhall/RTclTkExamples/evalRcode.html)的小部件的最小示例:

require(tcltk)

tkscript <- function() {
    tt <- tktoplevel()
    txt <- tktext(tt, height=10)
    tkpack(txt)
    run <- function() {
        code <- tclvalue(tkget(txt,"0.0","end"))
        e <- try(parse(text=code))
        if (inherits(e, "try-error")) {
            tkmessageBox(message="Syntax error", icon="error")
            return()
        }
        print(eval(e))
    }
    tkbind(txt,"<Control-r>",run)
}

tkscript()
Run Code Online (Sandbox Code Playgroud)

在脚本小部件中,如果您尝试执行Sys.sleep(20)然后从控制台中断,则小部件将挂起.如果要运行一个相同的事情,例如,无限循环,就像while(TRUE) 2+2.

我认为我所遇到的可能类似于此处报告的错误:https://bugs.r-project.org/bugzilla3/show_bug.cgi?id = 14730

另外,我应该提一下,我在Windows(x64)上的R 3.0.0上运行它,所以问题可能是特定于平台的.

有关如何在不导致小部件挂起的情况下中断正在运行的脚本的任何想法?

Don*_*ows 6

这取决于脚本正在做什么; 正在等待用户执行某项操作的脚本很容易被中断(因为您可以让它监听中断消息),但是执行密集循环的脚本则相当棘手.可能的解决方案取决于内部的Tcl版本.

译员取消 - 需要8.6

如果您使用的是Tcl 8.6,则可以使用解释器取消来停止脚本.您所要做的就是安排:

interp cancel -unwind
Run Code Online (Sandbox Code Playgroud)

要运行,脚本会将控制权返还给您.一个合理的方法是使用额外的Tcl包TclX(或Expect)来安装一个信号处理程序,它将在收到信号时运行该命令:

package require Tcl 8.6
package require TclX

# Our signal handler
proc doInterrupt {} {
    # Print a message so you can see what's happening
    puts "It goes boom!"
    # Unwind the stack back to the R code
    interp cancel -unwind
}

# Install it...
signal trap sigint doInterrupt

# Now evaluate the code which might try to run forever
Run Code Online (Sandbox Code Playgroud)

在早期版本中添加信号处理是可能的,但不是那么容易,因为你无法保证事情会如此轻易地将控制权交还给你; 堆栈展开不存在.

执行时间限制 - 要求8.5(或8.6)

您可以尝试的另一件事是在从属解释器上设置执行时间限制并在该从属服务器中运行用户脚本.时间限制机器将保证每隔一段时间回收一次,让您有机会检查中断和堆栈展开的方法.这是一种相当复杂的方法.

proc nextSecond {} {
    clock add [clock seconds] 1 second
}
interp create child
proc checkInterrupt {} {
    if {["decide if the R code wanted an interrupt"]} {
        # Do nothing
        return
    }
    # Reset the time limit to another second ahead
    interp limit child time -seconds [nextSecond]
}

interp limit child time -seconds [nextSecond] -command checkInterrupt
interp eval child "the user script"
Run Code Online (Sandbox Code Playgroud)

可以认为这种机制与操作系统的工作方式非常相似,是的,它可以阻止紧密循环.

使用子进程 - 任何版本的Tcl

最便携的机制是在子进程中运行脚本(使用tclsh程序;确切的名称因版本,平台和分布而异,但它的所有变化都是如此)并且只是在不再需要它时终止该子进程pskill.这样做的缺点是你不能(轻易地)将任何状态从一个执行转移到另一个执行; 子进程相互隔离.上面描述的其他方法可以使状态能够从另一个运行中访问:它们执行真正的中断,而这会破坏.

另外,我不确切知道如何以这样的方式启动子进程,以便在R仍在运行时可以与R进行通信; system并且system2似乎没有给予足够的控制,并且用分叉来破解某些东西是不可移植的.这里需要R专家.或者,使用Tcl脚本(在R进程内运行)来执行以下操作:

set executable "tclsh";    # Adjust this line
set scriptfile "file/where/you/put/the_user/script.tcl"

# Open a bi-directional pipe to talk to the subprocess
set pipeline [open |[list $executable $scriptfile] "r+"]

# Get the subprocess's PID
set thePID [pid $pipeline]
Run Code Online (Sandbox Code Playgroud)

这对Windows来说实际上是可移植的(如果不是那么完美的话),但带有分叉的中间状态则不然.