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.
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
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.
Related
I need a way to print a timer every second and execute an action every 10 seconds.
The output of the program should be like below.
Timer is 1
Timer is 2
Timer is 3
Timer is 4
Timer is 5
Timer is 6
Timer is 7
Timer is 8
Timer is 9
Timer is 10
Action is executed
Timer is 1
Timer is 2
Timer is 3
Timer is 4
Timer is 5
Timer is 6
Timer is 7
Timer is 8
Timer is 9
Timer is 10
Action is executed
Timer is 1
Timer is 2
Timer is 3
.
.
.
The program should use threading. It should not be an infinite while loop.
I could have done it with below code but it uses a global variable. How can I do it without using a global variable and with a small amount of code like below.
import threading
import time
global MytTimer
MytTimer=0
def foo():
global MytTimer
MytTimer=MytTimer+1
print("Timer is " + str(MytTimer))
threading.Timer(1, foo).start()
if MytTimer >= 10:
MytTimer=0
print("Action is executed")
foo()
I did it by creating a class.
import threading
import time
class count():
def __init__(self, MytTimer):
self.MytTimer = MytTimer
self._run()
def _run(self):
threading.Timer(1, self._run).start()
self.MytTimer += 1
print("Timer is " + str(self.MytTimer))
if self.MytTimer >= 10:
self.MytTimer=0
print("Action is executed")
a=count(MytTimer = 0)
You could create a ticker thread that delivers values 1-10 to a queue, and then a consumer that executes an action whenever the value read from the queue is 10:
import threading
import time
import queue
def foo():
q = queue.Queue()
def ticker():
while True:
for i in range(1,11):
print(f'Timer is {i}')
q.put(i)
time.sleep(1)
t_ticker = threading.Thread(target=ticker)
t_ticker.start()
while True:
i = q.get()
if i == 10:
print("Action is executed")
foo()
I have an infinite loop that immediately goes to sleep for one minute and then displays a message, but the problem is that when I stop the loop, the sleep() function works and the message is displayed at the end. Is it possible to reset sleep() after stopping the loop immediately?
from time import sleep
i = int(input())
flag = True
while flag:
if i < 0:
flag = False
sleep(60)
print('Hello, world')
you will likely need to implement a "special interruptable sleep" ... something like this could be a naive implementation that "works"
def do_something():
pass
class Program:
flag = True
def stoppable_sleep(self,t):
endTime = time.time() + t
while time.time() < endTime and self.flag:
time.sleep(0.1)
def mainloop(self):
while flag:
do_something()
self.stoppable_sleep(60)
print("Done...")
def stop(self):
self.flag = False
p = Program()
threading.Timer(5,p.stop)
p.mainloop()
I want to run a python while-loop for x amount of time (2 seconds) and then stop:
from PyQt6 import QtCore
def loop():
stop = False
def stop_loop():
nonlocal stop
stop = True
print('STOPPING LOOP')
timer = QtCore.QTimer()
timer.setSingleShot(True)
timer.setInterval(2000)
timer.timeout.connect(stop_loop)
timer.start()
counter = 0
while 1:
counter += 1
print(counter)
if stop:
break
app = QtCore.QCoreApplication([])
loop()
app.exec_()
But the loop keeps running infinitely and never stops.
The QTimer posts an event to the event-queue when it times-out, but your while-loop blocks all processing of events, so the timer-event never gets processed. To get your example to work, you would need to periodically force processing of any pending events:
def loop():
...
while 1:
counter += 1
print(counter)
# clear event-queue
app.processEvents()
if stop:
break
# do some more work...
QtCore.QThread.msleep(100)
Whilst this method works for some simple cases, it's not usually very scalable, so the preferred approach is to move the blocking task into a separate worker thread, like this:
from PyQt6 import QtCore
class Thread(QtCore.QThread):
def stop(self):
self._stopped = True
print('STOPPING LOOP')
def run(self):
self._stopped = False
counter = 0
while not self._stopped:
counter += 1
print(counter)
# do some more work...
self.msleep(100)
app = QtCore.QCoreApplication(['Test'])
thread = Thread()
thread.finished.connect(app.quit)
QtCore.QTimer.singleShot(2000, thread.stop)
thread.start()
app.exec()
How can I start and stop a thread with my poor thread class?
It is in loop, and I want to restart it again at the beginning of the code. How can I do start-stop-restart-stop-restart?
My class:
import threading
class Concur(threading.Thread):
def __init__(self):
self.stopped = False
threading.Thread.__init__(self)
def run(self):
i = 0
while not self.stopped:
time.sleep(1)
i = i + 1
In the main code, I want:
inst = Concur()
while conditon:
inst.start()
# After some operation
inst.stop()
# Some other operation
You can't actually stop and then restart a thread since you can't call its start() method again after its run() method has terminated. However you can make one pause and then later resume its execution by using a threading.Condition variable to avoid concurrency problems when checking or changing its running state.
threading.Condition objects have an associated threading.Lock object and methods to wait for it to be released and will notify any waiting threads when that occurs. Here's an example derived from the code in your question which shows this being done. In the example code I've made the Condition variable a part of Thread subclass instances to better encapsulate the implementation and avoid needing to introduce additional global variables:
from __future__ import print_function
import threading
import time
class Concur(threading.Thread):
def __init__(self):
super(Concur, self).__init__()
self.iterations = 0
self.daemon = True # Allow main to exit even if still running.
self.paused = True # Start out paused.
self.state = threading.Condition()
def run(self):
self.resume()
while True:
with self.state:
if self.paused:
self.state.wait() # Block execution until notified.
# Do stuff...
time.sleep(.1)
self.iterations += 1
def pause(self):
with self.state:
self.paused = True # Block self.
def resume(self):
with self.state:
self.paused = False
self.state.notify() # Unblock self if waiting.
class Stopwatch(object):
""" Simple class to measure elapsed times. """
def start(self):
""" Establish reference point for elapsed time measurements. """
self.start_time = time.time()
return self
#property
def elapsed_time(self):
""" Seconds since started. """
try:
return time.time() - self.start_time
except AttributeError: # Wasn't explicitly started.
self.start_time = time.time()
return 0
MAX_RUN_TIME = 5 # Seconds.
concur = Concur()
stopwatch = Stopwatch()
print('Running for {} seconds...'.format(MAX_RUN_TIME))
concur.start()
while stopwatch.elapsed_time < MAX_RUN_TIME:
concur.resume()
# Can also do other concurrent operations here...
concur.pause()
# Do some other stuff...
# Show Concur thread executed.
print('concur.iterations: {}'.format(concur.iterations))
This is David Heffernan's idea fleshed-out. The example below runs for 1 second, then stops for 1 second, then runs for 1 second, and so on.
import time
import threading
import datetime as DT
import logging
logger = logging.getLogger(__name__)
def worker(cond):
i = 0
while True:
with cond:
cond.wait()
logger.info(i)
time.sleep(0.01)
i += 1
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s %(threadName)s] %(message)s',
datefmt='%H:%M:%S')
cond = threading.Condition()
t = threading.Thread(target=worker, args=(cond, ))
t.daemon = True
t.start()
start = DT.datetime.now()
while True:
now = DT.datetime.now()
if (now-start).total_seconds() > 60: break
if now.second % 2:
with cond:
cond.notify()
The implementation of stop() would look like this:
def stop(self):
self.stopped = True
If you want to restart, then you can just create a new instance and start that.
while conditon:
inst = Concur()
inst.start()
#after some operation
inst.stop()
#some other operation
The documentation for Thread makes it clear that the start() method can only be called once for each instance of the class.
If you want to pause and resume a thread, then you'll need to use a condition variable.
How can I start and stop a thread with my poor thread class?
It is in loop, and I want to restart it again at the beginning of the code. How can I do start-stop-restart-stop-restart?
My class:
import threading
class Concur(threading.Thread):
def __init__(self):
self.stopped = False
threading.Thread.__init__(self)
def run(self):
i = 0
while not self.stopped:
time.sleep(1)
i = i + 1
In the main code, I want:
inst = Concur()
while conditon:
inst.start()
# After some operation
inst.stop()
# Some other operation
You can't actually stop and then restart a thread since you can't call its start() method again after its run() method has terminated. However you can make one pause and then later resume its execution by using a threading.Condition variable to avoid concurrency problems when checking or changing its running state.
threading.Condition objects have an associated threading.Lock object and methods to wait for it to be released and will notify any waiting threads when that occurs. Here's an example derived from the code in your question which shows this being done. In the example code I've made the Condition variable a part of Thread subclass instances to better encapsulate the implementation and avoid needing to introduce additional global variables:
from __future__ import print_function
import threading
import time
class Concur(threading.Thread):
def __init__(self):
super(Concur, self).__init__()
self.iterations = 0
self.daemon = True # Allow main to exit even if still running.
self.paused = True # Start out paused.
self.state = threading.Condition()
def run(self):
self.resume()
while True:
with self.state:
if self.paused:
self.state.wait() # Block execution until notified.
# Do stuff...
time.sleep(.1)
self.iterations += 1
def pause(self):
with self.state:
self.paused = True # Block self.
def resume(self):
with self.state:
self.paused = False
self.state.notify() # Unblock self if waiting.
class Stopwatch(object):
""" Simple class to measure elapsed times. """
def start(self):
""" Establish reference point for elapsed time measurements. """
self.start_time = time.time()
return self
#property
def elapsed_time(self):
""" Seconds since started. """
try:
return time.time() - self.start_time
except AttributeError: # Wasn't explicitly started.
self.start_time = time.time()
return 0
MAX_RUN_TIME = 5 # Seconds.
concur = Concur()
stopwatch = Stopwatch()
print('Running for {} seconds...'.format(MAX_RUN_TIME))
concur.start()
while stopwatch.elapsed_time < MAX_RUN_TIME:
concur.resume()
# Can also do other concurrent operations here...
concur.pause()
# Do some other stuff...
# Show Concur thread executed.
print('concur.iterations: {}'.format(concur.iterations))
This is David Heffernan's idea fleshed-out. The example below runs for 1 second, then stops for 1 second, then runs for 1 second, and so on.
import time
import threading
import datetime as DT
import logging
logger = logging.getLogger(__name__)
def worker(cond):
i = 0
while True:
with cond:
cond.wait()
logger.info(i)
time.sleep(0.01)
i += 1
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s %(threadName)s] %(message)s',
datefmt='%H:%M:%S')
cond = threading.Condition()
t = threading.Thread(target=worker, args=(cond, ))
t.daemon = True
t.start()
start = DT.datetime.now()
while True:
now = DT.datetime.now()
if (now-start).total_seconds() > 60: break
if now.second % 2:
with cond:
cond.notify()
The implementation of stop() would look like this:
def stop(self):
self.stopped = True
If you want to restart, then you can just create a new instance and start that.
while conditon:
inst = Concur()
inst.start()
#after some operation
inst.stop()
#some other operation
The documentation for Thread makes it clear that the start() method can only be called once for each instance of the class.
If you want to pause and resume a thread, then you'll need to use a condition variable.