3

tcltkパッケージを使用して R 用のスクリプト ウィジェットを作成しようとしています。しかし、ウィジェットからのスクリプトを中断するための STOP ボタンを作成する方法がわかりません。基本的に、現在のスクリプトの実行を中断するボタン、メニュー オプション、および/またはキー バインドが必要ですが、それを機能させる方法がわかりません。

1 つの (非理想的な) 戦略は、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()

スクリプティング ウィジェットで、実行を試みSys.sleep(20)てからコンソールから中断すると、ウィジェットがハングします。のような無限ループなどを実行すると、同じことが起こりますwhile(TRUE) 2+2

私が経験していることは、ここで報告されたバグに似ていると思います: https://bugs.r-project.org/bugzilla3/show_bug.cgi?id=14730

また、これを Windows (x64) の R 3.0.0 で実行していることに言及する必要があるため、問題はプラットフォーム固有のものである可能性があります。

ウィジェットをハングさせずに実行中のスクリプトを中断する方法について何か考えはありますか?

4

2 に答える 2

6

スクリプトの実行内容によって異なります。ユーザーが何かをするのを待っているスクリプトは簡単に中断できます (中断メッセージをリッスンできるため) が、集中的なループを実行しているスクリプトはかなりトリッキーです。考えられる解決策は、内部の Tcl のバージョンによって異なります。

通訳者のキャンセル — 8.6 が必要

Tcl 8.6 を使用している場合は、インタープリター キャンセルを使用してスクリプトを停止できます。あなたがしなければならないのは、次のことを手配することだけです:

interp cancel -unwind

実行すると、スクリプトは制御を返します。これを行う合理的な方法は、追加の 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

以前のバージョンでシグナル処理を追加することは可能ですが、物事が簡単に制御を返すことを保証できないため、それほど簡単ではありません。スタックの巻き戻しはありません。

実行時間制限 — 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"

このメカニズムは、オペレーティング システムの仕組みによく似ていると考えてください。そうです、タイト ループを止めることができます。

サブプロセスを使用 — Tcl の任意のバージョン

最も移植性の高いメカニズムは、サブプロセスでスクリプトを実行し (tclshプログラムで。正確な名前はバージョン、プラットフォーム、およびディストリビューションによって異なりますが、すべてのバリエーションです)、そのサブプロセスが不要になったら、そのサブプロセスを強制終了しpskillます。これの欠点は、ある実行から別の実行に (簡単に) 状態を引き継ぐことができないことです。サブプロセスは互いにかなり分離されています。上記の他の方法は、別の実行からアクセスできる状態を残すことができます。これらは実際の割り込みを行いますが、これは破棄します。

また、実行中に 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]

これは実際にはWindowsにかなり移植可能ですが(完全ではないにしても)、フォークを伴う中間状態はそうではありません。

于 2013-06-01T07:37:23.397 に答える
1

割り込み条件を処理evalする aに を埋め込むことで、tk ウィジェットがハングしないようにする解決策を見つけたようです。tryCatch残念ながら、ウィジェットではなくコンソールからの中断が必要ですが、機能します。tryCatchの文書化はかなり不十分なので、他の誰かが将来同様のニーズを持っている場合に備えて、ここにこれを掲載しています。

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="Parse error", icon="error")
            tkfocus(txt)
            return()
        }
        e <- tryCatch(eval(e),
            error = function(errmsg)
                tkmessageBox(message=as.character(errmsg), icon="error"),
            interrupt = function(errmsg)
                tkmessageBox(message=as.character(errmsg), icon="error")
        )
        print(eval(e))
    }
    tkbind(txt,"<Control-r>",run)
}

tkscript()

私が遭遇した別の戦略は、tools::pskill(tk メニュー オプションとしての形式でpskill(Sys.getpid(), SIGINT)) を使用してプロセスを中断することですが、少なくとも Windows では、これにより R プロセス全体 (tk ウィジェットを含む) が終了します。したがって、それは優れた解決策ではありませんが、少なくとも絶対的なフォールバックとしてすべてを終了するようです.

于 2013-06-02T13:44:17.587 に答える