tags:

views:

82

answers:

2

I have written a small Python application where I use PyGame for displaying some simple graphics.

I have a somewhat simple PyGame loop going in the base of my application, like so:

stopEvent = Event()

# Just imagine that this eventually sets the stopEvent
# as soon as the program is finished with its task.
disp = SortDisplay(algorithm, stopEvent)

def update():
    """ Update loop; updates the screen every few seconds. """
    while True:
        stopEvent.wait(options.delay)
        disp.update()
        if stopEvent.isSet():
            break
        disp.step()

t = Thread(target=update)
t.start()

while not stopEvent.isSet():
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            stopEvent.set()

It works all fine and dandy for the normal program termination; if the PyGame window gets closed, the application closes; if the application finishes its task, the application closes.

The trouble I'm having is, if I Ctrl-C in the Python console, the application throws a KeyboardInterrupt, but keeps on running.

The question would therefore be: What have I done wrong in my update loop, and how do I rectify it so a KeyboardInterrupt causes the application to terminate?

+1  A: 

What about changing your final loop to...:

while not stopEvent.isSet():
    try:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                stopEvent.set()
    except KeyboardInterrupt:
        stopEvent.set()

i.e., make sure you catch keyboard interrupts and treat them just the same as a quit event.

Alex Martelli
This would appear to work, as long as I also do the same inside the thread itself. It seems a bit strange to me that I have to explicitly catch the KeyboardInterrupt, though.
Sebastian P.
@Sebastian, KeyboardInterrupt always goes to the main thread, so I'm not sure why you should also catch it in secondary threads.
Alex Martelli
Indeed it would appear so, can't seem to replicate what I got earlier now. Maybe my files were out of sync. Thanks.
Sebastian P.
A: 

Amending Alex's answer, note that you probably want to do this on all exceptions, to ensure that you shut down the thread if the main thread fails for any reason, not just KeyboardInterrupt.

You also need to move the exception handler out, to avoid race conditions. For example, there might be a KeyboardInterrupt while calling stopEvent.isSet().

try:
    t = Thread(target=update)
    t.start()

    while not stopEvent.isSet():
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                stopEvent.set()
finally:
    stopEvent.set()

Doing this in finally makes it clearer: you can tell immediately that the event will always be set regardless of how you exit this code block. (I'm assuming setting the event twice is harmless.)

If you don't want to show a stack trace on KeyboardError you should catch it and swallow it, but be sure to do that only in your outermost code to be sure the exception is propagated out fully.

Glenn Maynard