2

我目前有一个 GUI,经过一些自动化(使用期望)后,用户可以与 10 个 telnet 连接之一进行交互。使用以下循环完成交互:

#After selecting an item from the menu, this allows the user to interact with that process
proc processInteraction {whichVariable id id_list user_id} {
    if {$whichVariable == 1} {
        global firstDead
        set killInteract $firstDead
    } elseif {$whichVariable == 2} {
        global secondDead
        set killInteract $secondDead
    }
    global killed

    set totalOutput ""
    set outputText ""
    #set killInteract 0
    while {$killInteract == 0} {
        set initialTrue 0
        if {$whichVariable == 1} {
            global firstDead
            set killInteract $firstDead
        } elseif {$whichVariable == 2} {
            global secondDead
            set killInteract $secondDead
        }
        puts "$id: $killInteract"
        set spawn_id [lindex $id_list $id]
        global global_outfile

        interact  {
            -i $spawn_id
            eof {
                set outputText "\nProcess closed.\n"
                lset deadList $id 1
                puts $outputText
                #disable the button
                disableOption $id $numlcp
                break
            }

            -re (.+) {
                set outputText $interact_out(0,string)
                append totalOutput $outputText
                #-- never looks at the following string as a flag
                send_user -- $outputText
                #puts $killInteract
                continue
            }

            timeout 1 {
                puts "CONTINUE"
                continue
            }        
        }
    }
    puts "OUTSIDE"
    if {$killInteract} {
        puts "really killed in $id"
        set killed 1
    }
}

选择新的过程时,应杀死先前的过程。我以前有它,如果单击一个按钮,它就会再次进入这个循环。最终我意识到 while 循环永远不会退出,并且在按下 124 次按钮后,它崩溃了(stackoverflow = P)。它们不是在后台运行,而是在堆栈上。所以我需要一种方法来在processInteraction新进程启动时终止函数中的循环。这是我在多次失败后最后一次尝试解决方案:

proc killInteractions {} {
    #global killed
    global killInteract
    global first
    global firstDead
    global secondDead
    global lastAssigned

    #First interaction
    if {$lastAssigned == 0} {
        set firstDead 0
        set secondDead 1
        set lastAssigned 1
        #firstDead was assigned last, kill the first process
    } elseif {$lastAssigned == 1} {
        set firstDead 1
        set secondDead 0
        set lastAssigned 2
        vwait killed
        #secondDead was assigned last, kill the second process
    } elseif {$lastAssigned == 2} {
        set secondDead 1
        set firstDead 0
        set lastAssigned 1
        vwait killed
    }

    return $lastAssigned
}

killInteractions按下按钮时调用。脚本挂起vwait。我知道代码对于处理具有两个变量的进程似乎有点奇怪/古怪,但这是让其工作的绝望的最后努力。

死信号被发送到正确的进程(以secondDeador的形式firstDead)。while我将交互的超时值设置为 1 秒,因此即使用户正在与该 telnet 会话进行交互,它也会被迫继续检查循环是否为真。一旦发送了 dead 信号,它就会等待确认进程已经死亡(通过vwait)。

问题是一旦发送信号,循环永远不会意识到它应该死掉,除非它被赋予上下文来检查它。first循环需要一直运行,直到它被or踢出secondDead。所以在切换到下一个进程之前需要有某种形式的等待,让前一个进程的循环processInteraction有控制权。

任何帮助将不胜感激。

4

1 回答 1

0

你的代码对我来说似乎非常复杂。但是,关键问题是您正在运行内部事件循环(事件循环代码非常简单,因此可以预见是一个问题)并使用卡住的东西构建 C 堆栈。你不想要那个!

让我们首先确定那些内部事件循环在哪里。首先,vwait是规范的事件循环命令之一;它运行一个事件循环,直到它的变量被设置(可能是由一个事件脚本)。然而,它并不是唯一的。特别是,Expectinteract 运行一个事件循环。这意味着一切都可能变得嵌套和纠结,而且……好吧,你不希望这样。(该页面谈到update,但它适用于所有嵌套的事件循环。)将事件循环放入您自己的事件循环while中特别容易导致调试问题。

解决此问题的最佳途径是重写代码以使用延续传递样式。不是使用嵌套事件循环编写代码,而是重新排列事物,以便您拥有对事件进行评估的代码片段,并在它们之间传递必要的状态,而无需启动嵌套事件循环。(如果您没有使用 Expect 并且使用的是 Tcl 8.6,我建议您使用coroutine它来执行此操作,但我认为当前不适用于 Expect,并且它确实需要尚未广泛部署的 Tcl 测试版.)

唉,由于需要与子流程进行交互,一切都变得更加复杂。没有办法在后台进行交互(也没有那么大的意义)。相反,您需要做的是interact在整个程序中只使用一个,并让它在生成的连接之间切换。您可以通过为-i选项提供一个全局变量的名称来实现这一点,该变量包含要与之交互的当前 id,而不是直接使用 id。(这是一个“间接”的 spawn id。)我认为使这项工作最简单的方法是拥有一个“不连接到任何其他东西”的 spawn id(例如,你连接它cat >/dev/null只是为了充当一个无所事事的角色)您在脚本开始时创建的,然后在有意义时交换实际连接。您当前使用的实际东西interact提防最好使用expect_background(记住使用expect_out而不是interact_out)。

您的代码太长了,我无法重写,但是您应该非常仔细地查看;eof子句的逻辑interact。它需要做的比目前更多。从 GUI 中杀死的代码也应该更改;它应该send是一个合适的 EOF 标记来生成要被杀死的进程,而不是等待确认死亡。

于 2012-06-13T08:26:22.687 回答