0

I'm developing a UI which has a Run button to run a certain tests on command prompt.I tried implementing threads here for invoking the command prompt and thought of monitoring the thread. Till the tests are run (on command prompt), I want the run button to be disabled and want to enable it only when the command prompt is closed.

I have created a .bat file to run the list of tests in command prompt.

The code is as follows:

Thread for invoking command prompt:

class RunMonitor(threading.Thread):
def run(self):
    print 'Invoking the command prompt .....'
    subprocess.call(["start", "/DC:\\Scripts", "scripts_to_execute.bat"], shell=True)

For Monitoring the thread

def runscript(self):
    print 'Complete_file_Path inside Run script is : ' , self.complete_file_path
    file_operation.Generate_Bat_File(self.complete_file_path)

    run_monitor_object = RunMonitor()
    run_monitor_object.start()

    while True:
        if run_monitor_object.isAlive():
            print 'The thread is still alive....'
        else:
            print 'The Thread is not alive anymore'
            self.run_button.setEnabled(True)
            break

From the above example, as soon as i invoke the command prompt, I run a while loop to monitor the status and I expect that the thread would be active as long as the command prompt is invoked and would be dead once I close the command prompt. But in my case, the application just hangs..

Few questions: 1. is this the right way to invoke thread? 2. is this the right way to monitor the status of the thread? 3. is there a better way of handling this ??

Any help towards this would be greatly appreciated.

Thanks.

user596922
  • 1,501
  • 3
  • 18
  • 27
  • Why are you using both a separate thread and a separate subprocess? A subprocess is usually sufficient to run something separate from your GUI. Why also use a separate thread? – S.Lott May 10 '11 at 11:03
  • I agree. But I would like to monitor the sub command prompt which I doubt can be done by sub process. As I said earlier, I have a run button in my GUI which has to be enabled only when the command prompt is closed. Can this be achieved without thread? – user596922 May 10 '11 at 11:19
  • 1
    @user596922: It sounds like your question *should* be "How do I monitor a separate subprocess's execution?" Perhaps searching for "monitoring subprocess" might be helpful. http://stackoverflow.com/questions/3869834/how-do-i-monitor-stdout-with-subprocess-in-python. – S.Lott May 10 '11 at 11:48

2 Answers2

2

The problem is how you're monitoring the thread. runscript blocks the program by executing in a loop as long as the thread is alive, thus blocking the GUI event system from processing user events - this creates the appearance of "hanging" you're experiencing.

There are many techniques for interacting with threads in GUI programming and PyQt in particular. The one I prefer in cases like this is using a timer. Once you start the work thread, also create a timer that invokes a method (slot) every 100 ms or so. This timer method does the monitoring - check if the work thread is still alive, etc. This won't block the event loop, for obvious reasons.

Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
  • I went through the threading.timer class and looks like we can call a function which can accept only a list or dictionary as an argument. I tried passing the object of the thread that I initially created (for invoking command prompt) but did not work . Exception in thread Thread-2: Traceback (most recent call last): File "C:\Python26\lib\threading.py", line 532, in __bootstrap_inner self.run() File "C:\Python26\WorkSpace\UI_Final\src\main.py", line 41, in run self.function(*self.args, **self.kwargs) TypeError: hello() argument after * must be a sequence, not RunMonitor – user596922 May 10 '11 at 16:03
  • No, use PyQt's timer, not `threading.Timer`. PyQt's timer fires signals which you can tie to slots - methods in your window class – Eli Bendersky May 10 '11 at 16:06
  • Hi Eli, I tried implementing with timers but still was not be successful. I was able to inkoke the command prompt in a thread, and able to monitor it but when the command prompt is closed, I was unable to communicate that to my main program and the thread stays alive for ever. Am i missing something here? A snippet of the code is below. – user596922 May 11 '11 at 05:43
  • self.timer = QTimer() self.timer.connect(self.timer, SIGNAL("timeout()"),self.sendData) self.timer.start(1000) def sendData(self): if self.run_timer: run_monitor_object = RunMonitor() print 'Starting the thread...........' run_monitor_object.start() if run_monitor_object.isRunning(): print 'The Thread is still Alive .......' if run_monitor_object.isFinished(): print 'The Thread is not alive anymore......' – user596922 May 11 '11 at 05:43
  • please stop pasting swaths of code into comments. this is unreadable. if you have a question about qtimer, create a new question and explain specifically what you don't understand – Eli Bendersky May 11 '11 at 06:55
2

I would almost recommend not using thread, however, if you still want to do that, one very simple way to communicate between your main program and your thread is via threading events. This is really just a boolean event (on or off), and you can take action when the Event is set. Here is your original program updated to use this:

class RunMonitor(threading.Thread): 
    def __init__(self, quit_event):
        threading.Thread.__init__(self)
        self.quit_event = quit_event
    def run(self):     
        print 'Invoking the command prompt .....'     
        subprocess.call(["start", "/DC:\\Scripts", "scripts_to_execute.bat"], shell=True)
        self.quit_event.set()

def runscript(self):     
    print 'Complete_file_Path inside Run script is : ' , self.complete_file_path     
    file_operation.Generate_Bat_File(self.complete_file_path)      

    quit_event = threading.Event()
    run_monitor_object = RunMonitor(quit_event).start()

    while True:         
        if not quit_event.is_set():
             print 'The thread is still alive....'         
        else:
             print 'The Thread is not alive anymore'             
             self.run_button.setEnabled(True)             
             break 

So, essentially, before you start the thread you create a threading.Event() object and pass that to your thread. Once this event is created you can .set() it to 'turn on' the Event, the main program simply waits for that to happen.

Like I say, this is very simple, it's just a boolean event. If you need something more complex you could add more events, or use a threading.Queue() instead.

[EDIT] Here is the fully working sample I created (rather than try to shoe-horn everything into your sample):

Here is the python file, note the changed to the subprocess.call line:

import threading
import subprocess
import time

class RunMonitor(threading.Thread): 
    def __init__(self, quit_event):
        threading.Thread.__init__(self)
        self.quit_event = quit_event
    def run(self):     
        print 'Invoking the command prompt .....\n'   
        subprocess.call(["start", "/WAIT", "/DC:\\python27\\sample", "xxx12.bat"], shell=True)                
        self.quit_event.set()

class Something:
    def runscript(self):     
        print 'Starting the thread...'  

        quit_event = threading.Event()
        run_monitor_object = RunMonitor(quit_event).start()

        while True:         
            if not quit_event.is_set():
                 print 'The thread is still alive....'         
            else:
                 print 'The Thread is not alive anymore'             
                 break 
            time.sleep(1)

runme = Something()
runme.runscript()

Note that I've added a sleep to the main loop so that the console doesn't fill up with "The thread is still alive..." messages.

Also, for reference here is my batch file (I named it xxx12.bat, as referenced in the python code), I just used this to cause delays so I could prove the thread was terminating correctly:

echo wscript.sleep 2500 > xxx12.vbs
start /wait xxx12.vbs
dir c:\
start /wait xxx12.vbs
dir c:\
start /wait xxx12.vbs
dir c:\
start /wait xxx12.vbs
dir c:\
start /wait xxx12.vbs
dir c:\
exit

The important thing to note here is that the 'exit' command in this batch file is vital, if we don't put that there the subprocess call will never terminate. Hopefully this example will help.

Raceyman
  • 1,354
  • 1
  • 9
  • 12
  • The above code does not work because the while loop is not allowing the program to invoke the command prompt and the application just hangs. Just curious.. was it working for you? – user596922 May 11 '11 at 05:37
  • I did have this working in a simplified manner (I was just using subprocess to get a directory), but the thread should be spawning before the while loop even starts. Are you certain the subprocess call is working on it's own? It almost sounds like that process either isn't starting, or isn't terminating. Could you try running that call outside of a thread just to ensure it terminates properly? If you still can't get it to work, I'll add another answer with the full code of my working sample. – Raceyman May 11 '11 at 13:14
  • Actually, I think I found the problem you're seeing, and it is the subprocess call. By doing `subprocess.call(["start", "/DC:\\Scripts", "scripts_to_execute.bat"], shell=True)` the 'start' is spawning a new shell which seems to cause subprocess to return immediately, without waiting for your batch file to complete. Can you try: `subprocess.call(["C:\\Scripts\\scripts_to_execute.bat"], shell=True)` instead? This should force subprocess to wait until the batch file is complete before it signals completion. – Raceyman May 11 '11 at 13:52
  • Sorry for all the extra comments. I did find a way to keep the 'start' in the subprocess command. `subprocess.call(["start", "/WAIT", "/DC:\\Scripts", "scripts_to_execute.bat"], shell=True)`, however, for this to work you need to ensure the very last command in the batch file is 'exit'. So, when I do this here, the batch file runs in a new command session, separate from the python session. I'll edit my original answer will the fully working source. – Raceyman May 11 '11 at 14:10