I have a function constantly running in a loop checking if it should start or cancel a timer that's defined in the function's scope. Timer needs to be defined within the function as the callback is also defined in the function. I'm able to start the timer fine, but when it tries to cancel, I get an error 'local variable 'timer' referenced before assignment'.

I've tried defining the timer and its callback in the global scope (which is undesirable), and I get another error 'threads can only be started once'.

import threading
import random

def start():
    trigger = random.randint(0,1)
    def do_something():
        print(trigger)
    if trigger == 0:
        timer = threading.Timer(2,do_something)
        timer.start()
    else:
        timer.cancel() #: if trigger is 1, I want to cancel the timer
    threading.Timer(1,start).start() #: start() is in a loop and is constantly checking trigger's value

start()

I want the same timer to be started or cancelled according to trigger's value. timer and its callback should be defined within the function.

2 Answers

0
quamrana On

This program shows how a random number can be used to start or stop a timer.

If the random number selects 0 enough times in a row, the timer will be started and be allowed to continue timing until time runs out and it calls its target.

If ever the random number selects 1, the timer is cancelled and the target is not called:

import threading
import random
import time

class Timing:
    def __init__(self):
        self.timer = None       # No timer at first
        self.something = None   # Nothing to print at first
        self.restart()

    def restart(self):
        self.run = threading.Timer(1.1, self.start)
        self.run.start()

    def cancel(self):
        if self.run is not None:
            self.run.cancel()
            self.run = None

    def start(self):
        trigger = random.randint(0, 1)
        self.do_start(trigger)

    def do_start(self, trigger):
        print('start', trigger)
        if trigger == 0:
            if self.timer is None:
                self.something = trigger
                self.timer = threading.Timer(2, self.do_something)
                self.timer.start()
        else:
            if self.timer is not None:
                self.timer.cancel()
                self.timer = None
                self.something=None
        self.restart()

    def do_something(self):
        print(self.something)

t = Timing()

print('sleeping...')
time.sleep(20)
t.cancel()
t.do_start(1)
t.cancel()
print('Done')

Sample output (ymmv because its random)

sleeping...
start 1
start 0
start 1
start 0
start 0
0
start 1
start 0
start 1
start 1
start 1
start 1
start 1
start 0
start 1
start 0
start 0
0
start 1
start 0
start 1
Done
0
moo5e On

I've learnt from @quamrana and @smci and came up with this

import threading
import random

class Timer():
    pass
t = Timer()

def start():
    trigger = random.randint(0,1)
    def do_something():
        print(trigger)
    if trigger == 0:
        t.timer = threading.Timer(1,do_something)
        t.timer.start()
    else:
        if hasattr(t,'timer'):
            t.timer.cancel() 
    threading.Timer(1,start).start() 

start()

This seems to solve the issue while keeping the code compact.