Home > Net >  How to use threads inside a class that act as a countdown timer?
How to use threads inside a class that act as a countdown timer?

Time:09-17

I want to define an object that contains an id, status and countdown timer. As soon as I instantiate this object, the countdown timer should set off immediately.

Below is the code where I create a class that has a non threaded version of the countdown timer defined in it

import time
import sys
from threading import Timer

class UserTimer:
    def __init__(self, id=None, current_status=None):
        self.id = id
        self.current_status = current_status
        self.timeout()

    def timeout(self):
        print("timeout started for", self.id)
        timeout_limit = 150
        seconds = 0
        while True:
            try:
                if seconds == timeout_limit:
                    print("countdown over for", self.id)
                    break
                time.sleep(1)
                seconds  = 1
            except KeyboardInterrupt, e:
                break

Below is how an instantiate it

params1 = {'id': "[email protected]", 'current_status': "success"}
params2 = {'id': "[email protected]", 'current_status': "success"}
user1 = UserTimer(**params1)
user2 = UserTimer(**params2)

The problem here is that when this program runs, it will instantiate the first object (user1) and due to the function time.sleep(), it will wait for the given duration before instantiating the second object (user2)

So I then looked up and found python threads helpful in this situation since the threads will run independent and won't block the progression of the code.

So this is how I changed the code below

class UserTimer:
    def __init__(self, id=None, current_status=None):
        self.id = id
        self.current_status = current_status

    def timeout(self):
        print("time over for", self.id)


    t = Timer(150, timeout)
    t.start()


params1 = {'id': "[email protected]", 'current_status': "success"}
params2 = {'id': "[email protected]", 'current_status': "success"}
user1 = UserTimer(**params1)
user2 = UserTimer(**params2)

Now both the objects get instantiated simultaneously but the problem is as soon as the given duration is over, it gives the following error

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 801, in __bootstrap_inner
    self.run()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 1073, in run
    self.function(*self.args, **self.kwargs)
TypeError: timeout() takes exactly 1 argument (0 given)

This is because it sees the keyword self which it doesn't expect. But I need the self since I need to dump some information about the user object. If I remove self, then it runs fine.

Am I defining threads inside a class in a wrong manner? What I want is to be able to instantiate multiple objects that have their own countdown timers.

Additionally I should also be able to reset the countdown timer by doing something like user1.reset_timer = True

What am I doing wrong here?

CodePudding user response:

I was getting a different error from your code than you were:

TypeError: timeout() missing 1 required positional argument: 'self'

The clue is the mention of self which was being caused by the:

    t = Timer(150, timeout)
    t.start()

you had in the class body of UserTimer, which means it gets executed when the class is defined (and no self instance has been created yet).

A simple way to fix that is to make instances of the class callable by defining a __call__() method (and calling it at the right time). Here's what I mean:

import time
import sys
from threading import Timer


class UserTimer:
    def __init__(self, id=None, current_status=None, interval=5):
        self.id = id
        self.current_status = current_status
        self.interval = interval

    def timeout(self):
        print("time over for", self.id)

    def __call__(self):
        self.timer_thread = Timer(self.interval, self.timeout)
        self.timer_thread.start()

    def cancel(self):
        try:
            self.timer_thread.cancel()
        except AttributeError:
            raise RuntimeError("'UserTimer' object not started.")


params1 = dict(id="[email protected]", current_status="success")
params2 = dict(id="[email protected]", current_status="success", interval=6)
user1 = UserTimer(**params1)
user1()  # Start Timer.
user2 = UserTimer(**params2)
user2()  # Start Timer.

I also added the cancel() method as you requested and made the time interval an easily changed variable instead of hardcoding it into the body of the class as well.

CodePudding user response:

You should put Timer initialization in __init__() to start it when you instantiate an object.

class UserTimer(object):
    def __init__(self, id=None, current_status=None):
        self.id = id
        self.current_status = current_status
        self.start_timer()   # start timer when an object is instantiated

    def timeout(self):
        print("time over for", self.id)

    def start_timer(self):
        self.t = Timer(5, self.timeout)  # changed to 5 seconds for demo                                                 
                                         # `self.timeout` instead of `timeout`
        self.t.start()

    def reset_timer(self):
        self.t.cancel()      # cancel old timer
        self.start_timer()   # and start a new one

params1 = {'id': "[email protected]", 'current_status': "success"}
params2 = {'id': "[email protected]", 'current_status': "success"}
user1 = UserTimer(**params1)

time.sleep(2)     # assuming that user2 is instantiated 2 seconds later
print('instantiating user2...')
user2 = UserTimer(**params2)

time.sleep(3)     # reset user1's timer 3 seconds after user2 instantiation
print('reseting timer of user1...')
user1.reset_timer()

You can run the code and check the timing.

  • Related