Pause and resume thread in python

Please remember that using threads in Pythin will not grant you a parallel processing, except for the case of IO blocking operations. For more information on this, take a look at this and this

You cannot pause a Thread arbitrarily in Python (please keep that in mind before reading further). I am neither sure you have a way to do that at an OS level (e.g. by using pure-C). What you can do is allow the thread to be paused at specific points you consider beforehand. I will give you an example:

class MyThread(threading.Thread):

    def __init__(self, *args, **kwargs):
        super(MyThread, self).__init__(*args, **kwargs)
        self._event = threading.Event()

    def run(self):
        while True:
            self.foo() # please, implement this.
            self._event.wait()
            self.bar() # please, implement this.
            self._event.wait()
            self.baz() # please, implement this.
            self._event.wait()

    def pause(self):
        self._event.clear()

    def resume(self):
        self._event.set()

This approach will work but:

  • Threading is usually a bad idea, based on the links I gave you.
  • You have to code the run method by yourself, with this approach. This is because you need to have control over the exact points you'd like to check for pause, and this implies accessing the Thread object (perhaps you'd like to create an additional method instead of calling self._event.wait()).
  • The former point makes clear that you cannot pause arbitrarily, but just when you specified you could pause. Avoid having long operations between pause points.

Edit I did not test this one, but perhaps this will work without so much subclassing if you need more than one thread like this:

class MyPausableThread(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        self._event = threading.Event()
        if target:
            args = (self,) + args
        super(MyPausableThread, self).__init__(group, target, name, args, kwargs)

    def pause(self):
        self._event.clear()

    def resume(self):
        self._event.set()

    def _wait_if_paused(self):
        self._event.wait()

This should allow you to create a custom thread without more subclassing, by calling MyPausableThread(target=myfunc).start(), and your callable's first parameter will receive the thread object, from which you can call self._wait_if_paused() when you need to pause-check.

Or even better, if you want to isolate the target from accessing the thread object:

class MyPausableThread(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        self._event = threading.Event()
        if target:
            args = ((lambda: self._event.wait()),) + args
        super(MyPausableThread, self).__init__(group, target, name, args, kwargs)

    def pause(self):
        self._event.clear()

    def resume(self):
        self._event.set()

And your target callable will receive in the first parameter a function that can be called like this: pause_checker() (provided the first param in the target callable is named pause_checker).


You can do this by attaching a trace function that causes all other threads to wait for a signal:

import sys
import threading
import contextlib

# needed to enable tracing
if not sys.gettrace():
    sys.settrace(lambda *args: None)

def _thread_frames(thread):
    for thread_id, frame in sys._current_frames().items():
        if thread_id == thread.ident:
            break
    else:
        raise ValueError("No thread found")
    # walk up to the root
    while frame:
        yield frame
        frame = frame.f_back


@contextlib.contextmanager
def thread_paused(thread):
    """ Context manager that pauses a thread for its duration """
    # signal for the thread to wait on
    e = threading.Event()

    for frame in _thread_frames(thread):
        # attach a new temporary trace handler that pauses the thread

        def new(frame, event, arg, old = frame.f_trace):
            e.wait()

            # call the old one, to keep debuggers working
            if old is not None:
                return old(frame, event, arg)
        frame.f_trace = new

    try:
        yield
    finally:
        # wake the other thread
        e.set()

Which you can use as:

import time

def run_after_delay(func, delay):
    """ Simple helper spawning a thread that runs a function in the future """
    def wrapped():
        time.sleep(delay)
        func()
    threading.Thread(target=wrapped).start()

main_thread = threading.current_thread()

def interrupt():
    with thread_paused(main_thread):
        print("interrupting")
        time.sleep(2)
        print("done")

run_after_delay(interrupt, 1)
start = time.time()
def actual_time(): return time.time() - start

print("{:.1f} == {:.1f}".format(0.0, actual_time()))
time.sleep(0.5)
print("{:.1f} == {:.1f}".format(0.5, actual_time()))
time.sleep(2)
print("{:.1f} != {:.1f}".format(2.5, actual_time()))

Giving

0.0 0.0
0.5 0.5
interrupting
done
2.5 3.0

Note how the interrupt causes the sleep on the main thread to wait longer