Python, threading.timer object will not run function timer?

12,007

Solution 1

As second argument to timer you should pass a callable, but in your case you are calling the function and passing the result to threading.Timer. You should use a lambda expression instead:

#!/usr/bin/env python

import time
import smtplib
import threading

if True:
    t = threading.Timer(300, lambda: send_email('Tank temperature Overheat',tank_temp))
    t.start() 
print "rest of the script keeps running"
print "keeps running the scrpit and after 300s the email is sent"

The expression:

lambda: send_email('Tank temperature Overheat',tank_temp)

Evaluates to a function without arguments, that when called executes send_email with that arguments, while in your code you have:

t = threading.Timer(300,send_email('Tank temperature Overheat',tank_temp))

This will first evaluate all the arguments, and hence call send_email, and then create the Timer object.


Regarding the issue with the 36 emails in 300 seconds, in your code:

n=300

start = time.time()

while (time.time() - start < n):

    led_temp = 56

        if led_temp > 55:
        t = threading.Timer(100, lambda: send_email('Lights temperature Overheat',led_temp))
        t.start()

The while will create tons of threads during the 300 seconds of iterations. I have no idea why you think this loop should send you an email every 100 seconds. Each thread will send you an email after 100 seconds, but there are a lot of threads.

If you want to send only 3 emails, then you should break out of the loop after three iterations. Also, the iterations are probably too fast, hence all timers will send all the emails almost at once, since their timeouts are almost equal.

You can see this problem with the following code:

>>> import threading
>>> import threading
>>> def send_email(): print("email sent!")
... 
>>> for _ in range(5):
...     t = threading.Timer(7, send_email)
...     t.start()
... 
>>> email sent!
email sent!
email sent!
email sent!
email sent!

Even though 5 different Timers are created, they timeout almost at the same time, so you'll see all the email sent! appear at the same time. You could modify the timeout to take this into account:

>>> for i in range(5):
...     t = threading.Timer(7 + i, send_email)
...     t.start()
... 
>>> email sent!
email sent!
email sent!
email sent!
email sent!

In the above code you'll see email sent! appear one at a time with an interval of about 1 second.

Lastly I'd add that you have no way to control when an email will be received. This is handled in different ways by the different services, so there is no way to guarantee that, when running your code, the recipient will receive an email every 100 seconds.

Solution 2

threading.Timer can be run in two modes:

The first option is to use the arguments provided by the method:

t = threading.Timer(interval=300, function=send_email, args=['Tank temperature Overheat', tank_temp])
t.start()

The second option is to use a "lambda function"

t = threading.Timer(interval=300, lambda: send_email('Tank temperature Overheat', tank_temp))
t.start()
Share:
12,007
user2520982
Author by

user2520982

Updated on June 04, 2022

Comments

  • user2520982
    user2520982 almost 2 years

    Im trying to run send an email with a delay, since the conditional to send the email can be ON for quite some time and I dont want to receive and infinite amount of email alerts...

    For this I'm trying the threading.timer to give it a delay and only send the email every 15 minutes... I tried the long 900 second delay on the .timer object and it works (used time script)... but when I run it to send the email it first sends the email and then enters the timer not running the other parts of the script... The email function works just fine... running python 2.6.6

    #!/usr/bin/env python
    
    import time
    import smtplib #for sending emails when warning
    import threading
    
    if True: #the possibility exists that the conditional is met several times thus sending lots of emails
        t = threading.Timer(300,send_email('Tank temperature Overheat',tank_temp))
        t.start() # after 300 seconds, the email will be sent but the script will keep running
    print "rest of the script keeps running"
    print "keeps running the scrpit and after 300s the email is sent"
    

    Any ideas on why its not working or another workaround?

    After playing with it... it does the sleep but the sends all the emails... not one email every X amount of .time set... i.e.

    n=300
    
    start = time.time()
    
    while (time.time() - start < n):
    
        led_temp = 56
    
            if led_temp > 55:
            t = threading.Timer(100, lambda: send_email('Lights temperature Overheat',led_temp))
            t.start()
    

    Instead of receiving one email every 100 seconds I get 36 emails after 300 seconds .. ?? Any idea why? (Reformat from comment below)

    After reading the answer on the threading I understood the problem... Im still knew to python and had never user threading so I guess that was the root cause of the 36 emails I received when creating endless amounts of threads... I fixed it by using a flag, and tested the code like this:

    def raise_flag():
        global start
        interval = 300
        if start > interval:
            start = 0
            flag = True
            print "Flag True, sending email"
            return flag
        else:
            flag = False
            start = start + 1
            print "Flag OFF", start
            time.sleep(1)
            return flag
    
    led_temp = 27
    while led_temp > 26:
        flag = raise_flag()
        if flag:
            send_email('Tank temperature Overheat',led_temp)
            flag = False
            print "Sent email"
    
  • user2520982
    user2520982 almost 11 years
    works like a charm.. I'm still new to python and had not seen the lambda function.. I'll do some reading
  • user2520982
    user2520982 almost 11 years
    After playing with it... it does the sleep but the sends all the emails... not one email every X amount of .time set... i.e. ' n=300 start = time.time() while (time.time() - start < n): led_temp = 56 if led_temp > 55: t = threading.Timer(100, lambda: send_email('Lights temperature Overheat',led_temp)) t.start() ' If I run this code Im simulating the temp warning to be above the value for 300 seconds. Instead of receiving one email every 100 seconds I get 36 emails after 300 seconds .. ?? Any idea why?
  • Bakuriu
    Bakuriu almost 11 years
    @user2520982 Can you edit your question and expand on what's the problem now? Code in the comments is not readable at all. AFAIK emails are not guaranteed to be instantaneous, so the providers etc may deliver more than one email at a time.
  • user2520982
    user2520982 almost 11 years
    I put the comment in the question... any inputs?