Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a graceful or Pythonic way to interrupt a time.sleep() call in a thread?

The code below works the way I expect it to work, namely:

  • there is a QThread ("Ernie") that counts from 1 to 8, sleeping for 1 second between counts
  • there is a gratuitous UI widget ("Bert")
  • under normal operation, the program runs until both the thread finishes and the UI is closed
  • a Ctrl-C keyboard interrupt will stop the program gracefully prior to normal completion.

To do this, I had to break up the 1-second sleep into 50 millisecond chunks that check a flag.

Is there a more Pythonic way to sleep in a thread for some amount of time (e.g. 1 second) but be interruptable by some flag or signal?

        try:
            for i in xrange(8):
                print "i=%d" % i
                for _ in xrange(20):
                    time.sleep(0.05)
                    if not self.running:
                        raise GracefulShutdown
        except GracefulShutdown:
            print "ernie exiting"        

I'd rather do this, and somehow cause a GracefulShutdown exception in the thread:

        try:
            for i in xrange(8):
                print "i=%d" % i
                time.sleep(1)
                # somehow allow another thread to raise GracefulShutdown
                # during the sleep() call
        except GracefulShutdown:
            print "ernie exiting"        

full program;

from PySide import QtCore, QtGui
from PySide.QtGui import QApplication
import sys
import signal
import time

class GracefulShutdown(Exception):
    pass

class Ernie(QtCore.QThread):
    def __init__(self):
        super(Ernie, self).__init__()
        self.running = True
    def run(self):
        try:
            for i in xrange(8):
                print "i=%d" % i
                for _ in xrange(20):
                    time.sleep(0.05)
                    if not self.running:
                        raise GracefulShutdown
        except GracefulShutdown:
            print "ernie exiting"        
    def shutdown(self):
        print "ernie received request to shutdown"
        self.running = False

class Bert(object):
    def __init__(self, argv):
        self.app = QApplication(argv)
        self.app.quitOnLastWindowClosed = False
    def show(self):
        widg = QtGui.QWidget()
        widg.resize(250, 150)
        widg.setWindowTitle('Simple')
        widg.show()
        self.widg = widg
        return widg
    def shutdown(self):
        print "bert exiting"
        self.widg.close()
    def start(self):
        # return control to the Python interpreter briefly every 100 msec
        timer = QtCore.QTimer()
        timer.start(100)
        timer.timeout.connect(lambda: None) 
        return self.app.exec_()        

def handleInterrupts(*actors):
    def handler(sig, frame):
        print "caught interrupt"
        for actor in actors:
            actor.shutdown()
    signal.signal(signal.SIGINT, handler)

bert = Bert(sys.argv)
gratuitousWidget = bert.show()
ernie = Ernie()
ernie.start()

handleInterrupts(bert, ernie)

retval = bert.start()
print "bert finished"
while not ernie.wait(100):
    # return control to the Python interpreter briefly every 100 msec
    pass
print "ernie finished"
sys.exit(retval)
like image 410
Jason S Avatar asked Jul 07 '14 18:07

Jason S


1 Answers

I am not sure how Pythonic it is but it works. Just use a queue and use blocking get with a timeout. See the example below:

import threading
import Queue
import time

q = Queue.Queue()


def workit():
    for i in range(10):
        try:
            q.get(timeout=1)
            print '%s: Was interrupted' % time.time()
            break
        except Queue.Empty:
            print '%s: One second passed' % time.time()


th = threading.Thread(target=workit)
th.start()

time.sleep(3.2)
q.put(None)
like image 174
Eugen Avatar answered Nov 10 '22 13:11

Eugen