Search code examples
pythonpython-multithreadinginterrupt

Stop a single I/O read file thread in python


How can I interrupt a read file operation? For example I'm trying to read a big tiff file on network drive and it can sometime take a long time. I want to be able to cancel the reading operation on a button click for example. I read about threading but I can't use an event here since it's not a loop, but a single read operation, and I can't periodically check for a stop event..

def start_reading(path):
    thread = threading.Thread(target=read_tiff(path))
    thread.start()

def read_tiff(path):
        start = time.time()
        print('Start reading\n')
        im = cv2.imread(file)
        print('Read finished: %.3f' % (time.time() - start))

def stop_reading():
....


file = 'random.tiff'
root = tk.Tk()

start_button = tk.Button(root, text="Start Reading", command=lambda: start_reading(file))
start_button.pack()

stop_button = tk.Button(root, text="Stop Reading", command=stop_reading)
stop_button.pack()

root.mainloop()

Solution

  • You can still use a threading.Event object to signal to the worker thread (read_tiff) if it should early exit in case of a user pressing the stop button. Since cv2.imread is blocking, I suggest streaming the network file into a tempory bytebuffer in increments of small chunks. That way the worker can continue checking the stop event, and early exit if requested by the user. In case the image downloads before the user presses the stop button, you can use numpy and cv2 APIs to decode the byte buffer back to a cv2 image object.

    import cv2
    import threading
    import time
    import numpy as np
    import tkinter as tk
    
    
    def start_reading(path, stop):
        stop.clear()
        thread = threading.Thread(target=read_tiff, args=(path, stop))
        thread.start()
    
    
    def read_tiff(path, stop):
            print("read thread started")
            start = time.monotonic()
            buf = bytearray()
            with open(path, 'br') as f:
                print('Start reading\n')
                chunk = f.read(1024)
                while chunk and not stop.is_set():
                    buf.extend(chunk)
                    chunk = f.read(1024)
            if stop.is_set():
                 print("Thread timed out")
                 return
            arr = np.frombuffer(buf, np.uint8)
            img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
            # write to output file to verify it works
            cv2.imwrite('output.tiff', img)
            print('Read finished: %.3f' % (time.monotonic() - start))
    
    file = 'random.tiff'
    root = tk.Tk()
    stop_event = threading.Event()
    
    start_button = tk.Button(
        root, 
        text="Start Reading", 
        command=lambda: start_reading(file, stop_event),
    )
    start_button.pack()
    
    stop_button = tk.Button(
        root, 
        text="Stop Reading", 
        command=lambda: stop_event.set()
    )
    stop_button.pack()
    
    root.mainloop()