Generic oneshot task runner I'm making some helper functions for the (fairly simple) UI framework I'm developing, among them, a helper to run app initialization tasks. Here's a helper class I've developed for oneshot tasks:

from threading import Lock

class Oneshot():
    _running = False
    _finished = False

    def __init__(self, func, *args, **kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs

    def run(self):
        if self.running or self.finished:
            return
        self.active_lock.acquire()
        self._running = True
        self.active_lock.release()

        self.func(*self.args, **self.kwargs)

        self.active_lock.acquire()
        self._running = False
        self._finished = True
        self.active_lock.release()

    @property
    def running(self):
        self.active_lock.acquire()
        value = self._running
        self.active_lock.release()
        return value

    @property
    def finished(self):
        self.active_lock.acquire()
        value = self._finished
        self.active_lock.release()
        return value


Is it actually thread-safe, as I intend it to be? Any other thoughts on this?

I'd also like to add a reset() method. My idea is like this:

def reset(self):
    if self.running:
        raise Exception("Runner can't be reset while still running")
    self._finished = False


My only doubt is about whether raising an exception is the best course of action, but, since I have to signal the user that they are trying to perform an illegal action, and it's better to do it explicitly as the action user desired did not complete, I don't see a better way - do you?
If run is called from multiple threads, the task may execute multiple times because this code is checking and setting the flag in separate operations:

    if self.running or self.finished:
        return
    self.active_lock.acquire()
    self._running = True
    self.active_lock.release()


Make it an atomic operation by holding the lock. Note also how the with statement releases the lock automatically:

    with self.active_lock:
        if self._running or self._finished:
            return
        self._running = True