Python | Periodic Task

Doing a periodic task is a basic programming requirement. We need it in time of implementing watchdogs, checking health of some system, sending status periodically, updating database, doing periodic backup etc.

Using Loops

When it comes to do something periodically, the first that comes to our mind is the ‘loop‘. First we’ll see how we can do something periodically using ‘loop‘.

import time, datetime, threading
TIME_TO_WAIT = 1
def foo():
  print ("Thread: %s, time: %s" % (threading.get_ident(), datetime.datetime.now()))
print("Calling thread: %s" % (threading.get_ident()))
while 1:
  foo()
  time.sleep(TIME_TO_WAIT)
print("Work done!")

In the above program, function foo() is the task we want to do periodically. In this function we simply printing the time stamp ( and the thread id – we’ll discuss the reason later).

We have an infinite ‘while‘ loop that calls the foo() function and waits for 1 second. That means that in every 1 second, the foo() function will be called.

$ python3 test.py
 Calling thread: 140313328641856
 Thread: 140313328641856, time: 2021-08-15 01:45:24.645855
 Thread: 140313328641856, time: 2021-08-15 01:45:25.647063
 Thread: 140313328641856, time: 2021-08-15 01:45:26.648214
 Thread: 140313328641856, time: 2021-08-15 01:45:27.649199
 Thread: 140313328641856, time: 2021-08-15 01:45:28.650850
 Thread: 140313328641856, time: 2021-08-15 01:45:29.652596

From the timestamps of the output above, we can see that the function foo() gets executed in every 1 second. I added the thread id with the output to point out a very important aspect. If you see these ids closely, you’ll notice that the function prints the same thread id as of the calling thread. That means the function (or the loop) is getting executed in the context of the calling thread. So, the calling thread is blocked for this periodic task.

If you look at the program, the ‘Work done!‘ will never get printed. So the calling thread can not do anything else. This is not desirable in most practical situations. You’ll need to other things from the calling thread while the periodic task is also taken care of.

In the following sections, we’ll see how we can do that.

Using threading.Timer()

Timer is a subclass that represents an action that will run only after a specified amount of time. We’ll use this to accomplish periodic task.

import datetime, threading

TIME_TO_WAIT = 1

def foo():
     print ("Thread: %s, time: %s" % (threading.get_ident(), datetime.datetime.now()))
     threading.Timer(TIME_TO_WAIT, foo).start()
print("Calling thread: %s" % (threading.get_ident()))
foo()
print("Work done!")

We did not use any loop here. In the foo() function, we are creating an object of threading.Timer() which will call the same foo() function after 1 second but in different thread context. This process will continue – the function will get called in every 1 second.

$ python3 test.py
 Calling thread: 140231117473600
 Thread: 140231117473600, time: 2021-08-15 01:40:50.710510
 Work done!
 Thread: 140230982440704, time: 2021-08-15 01:40:51.712205
 Thread: 140230974048000, time: 2021-08-15 01:40:52.714167
 Thread: 140230982440704, time: 2021-08-15 01:40:53.715436
 Thread: 140230974048000, time: 2021-08-15 01:40:54.716516
 Thread: 140230982440704, time: 2021-08-15 01:40:55.718045
 Thread: 140230974048000, time: 2021-08-15 01:40:56.719752
 Thread: 140230982440704, time: 2021-08-15 01:40:57.721679

From this output, we can see that the function is executed from the calling thread function for the first time. The thread ids are same – 140231117473600. After that the calling thread is released. The ‘Work done!’ gets printed. The thread id of every function call is different. That means that the function is getting executed in different thread context every time.

This is not also very efficient to create a new thread every time.

We can avoid that by adding a loop in the function and creating the threading.Timer() object from the calling thread.

Here is the modified code.

 import time, datetime, threading
 TIME_TO_WAIT = 1
 def foo():
   while 1:
     print ("Thread: %s, time: %s" % (threading.get_ident(), datetime.datetime.now()))
     time.sleep(TIME_TO_WAIT)
 print("Calling thread: %s" % (threading.get_ident()))
 threading.Timer(0, foo).start()
 print("Work done!")
$ python3 test.py
 Calling thread: 140652255508288
 Thread: 140652120475392, time: 2021-08-15 01:51:34.585125
 Work done!
 Thread: 140652120475392, time: 2021-08-15 01:51:35.586540
 Thread: 140652120475392, time: 2021-08-15 01:51:36.587470
 Thread: 140652120475392, time: 2021-08-15 01:51:37.589661
 Thread: 140652120475392, time: 2021-08-15 01:51:38.591788
 Thread: 140652120475392, time: 2021-08-15 01:51:39.593764

From this output, we can see that the function is not getting executed in the calling thread. The calling thread is released to do other things – like it printed ‘Work done!’. The foo() function is printing the timestamp from the same threading context.

The same thing can be achieved by creating a threading object instead of threading.Timer().

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

Your email address will not be published. Required fields are marked *

0
0
0
0
0
2