Search code examples
pythonraspberry-pisigint

python picamera, keyboard ctrl+c/sigint not caught


From the pycamera docs I took the example for fast capture and processing and added a sigint event handler to catch the keyboard interrupt:

import io
import time
import threading
import picamera

# Create a pool of image processors
done = False
lock = threading.Lock()
pool = []

def signal_handler(signal, frame):
    global done
    print 'You pressed Ctrl+C!'
    done=True
    sys.exit()

signal.signal(signal.SIGINT, signal_handler)
class ImageProcessor(threading.Thread):
    def __init__(self):
        super(ImageProcessor, self).__init__()
        self.stream = io.BytesIO()
        self.event = threading.Event()
        self.terminated = False
        self.daemon=True;
        self.start()

    def run(self):
        # This method runs in a separate thread
        global done
        while not self.terminated:
            # Wait for an image to be written to the stream
            if self.event.wait(1):
                try:
                    self.stream.seek(0)
                    # Read the image and do some processing on it
                    #Image.open(self.stream)
                    #...
                    #...
                    # Set done to True if you want the script to terminate
                    # at some point
                    #done=True
                finally:
                    # Reset the stream and event
                    self.stream.seek(0)
                    self.stream.truncate()
                    self.event.clear()
                    # Return ourselves to the pool
                    with lock:
                        pool.append(self)

def streams():
    while not done:
        with lock:
            if pool:
                processor = pool.pop()
            else:
                processor = None
        if processor:
            yield processor.stream
            processor.event.set()
        else:
            # When the pool is starved, wait a while for it to refill
            time.sleep(0.1)

with picamera.PiCamera() as camera:
    pool = [ImageProcessor() for i in range(4)]
    camera.resolution = (640, 480)
    camera.framerate = 30
    camera.start_preview()
    time.sleep(2)
    camera.capture_sequence(streams(), use_video_port=True)

# Shut down the processors in an orderly fashion
while pool:
    with lock:
        processor = pool.pop()
    processor.terminated = True
    processor.join()

but the interrupt signal is never caught.

Until the camera.capture_sequence(streams(), use_video_port=True) runs the signal is caught, after capture_sequence is started the signal handler is not called.

I'm new to python so maybe the answer is simple. What am i doing wrong in here?

EDIT:

If i remove the following code the signal is caught:

 yield processor.stream

Solution

  • The problem there is that you are using thread.join(), it block the main thread,which means your program have to wait until that thread you joined finishes to continue.

    The signals will always be caught by the main process, because it's the one that receives the signals, it's the process that has threads.

    There are plenty of answer about how to deal with main thread and CTRL+C,and i give you three options,

    First,add timeout to join() call:

    thread1.join(60) detail here

    Second, start a new process to deal with signal to kill the program.

    class Watcher():  
      
        def __init__(self):  
            self.child = os.fork()  
            if self.child == 0:  
                return  
            else:  
                self.watch()  
      
        def watch(self):  
            try:  
                os.wait()  
            except KeyboardInterrupt:  
                self.kill()  
            sys.exit()  
      
        def kill(self):  
            try:  
                os.kill(self.child, signal.SIGKILL)  
            except OSError:  
                pass  
    

    start a Watcher before you start work thread,like

    def main():  
        init()  
        Watcher()  
        start_your_thread1()  
        start_your_thread2()  
        start_your_thread3() 
    

    The final,your original way,the complicate Producer and Consumer way.

    just delete the final join(),and add some task for the main thread.

    i prefer the second option,it's easy use,and solves two problems with multithreaded programs in Python, (1) a signal might be delivered to any thread (which is just a malfeature) and (2) if the thread that gets the signal is waiting, the signal is ignored (which is a bug). More detail about the Watcher is in Appendix A of the book The Little Book of Semaphores