Generic oneshot task runner
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