Python How to Implement Threading of my Email Class

738 views Asked by At

I have a pretty large email class that constructs & sends various emails with mime, attachments etc.

Everything works fine but it is called from my main loop and the sendmail method using smtplib can sometimes block for several seconds because of issues at the other end.

I want to run the code in a new thread so my main loop can keep trucking.

I have tried two approaches without success:

  • Calling my class using a Thread

  • Inherit my V1 class from Thread

Neither stop the blocking. Below is a working skeleton of my class (V1) and what I have tried. Success would be to see "Done" appear immediately after "Starting" instead of waiting the 3 seconds for sendmail to finish. Hoping there is an easy way to do this...

from time import sleep
from threading import Thread

class EMailv1():
    def __init__(self):
        self._from_address = '[email protected]'
        self._to_addresslist = []

    def buildmessage(self, **kwargs):
        message ={}
        return message

    @staticmethod
    def sendmail(message):
        sleep(5)
        return

class EMailv2(Thread):
    def __init__(self):
        Thread.__init__(self)
        self._from_address = '[email protected]'
        self._to_addresslist = []

    def run(self):
        pass

    def buildmessage(self, **kwargs):
        message ={}
        return message

    @staticmethod
    def sendmail( message):
        sleep(3)
        return


if __name__ == "__main__":
    print('Starting V1 class send in current manner, which blocks if smtplib cannot deliver mail')
    email =EMailv1()
    msg = email.buildmessage(a='a',b='b')
    email.sendmail(msg)
    print('v1 Done after sendmail sleep finishes')

    print('Starting  threaded call to send V1 class')
    email = EMailv1()
    msg = email.buildmessage(a='a',b='b')
    t = Thread(target=email.sendmail(msg))
    t.start()
    print('Threaded call of V1 Done after sendmail sleep finishes')

    print('Starting V2 class inheriting Thread')
    email = EMailv2()
    msg = email.buildmessage(a='a',b='b')
    email.start()
    email.sendmail(msg)
    print('V2 Done after sendmail sleep finishes')
1

There are 1 answers

1
Cihan On BEST ANSWER

In your second version, instead of

t = Thread(target=email.sendmail(msg))

you should do

t = Thread(target=email.sendmail, args=[msg])

In the way you wrote, it is evaluating email.sendmail(msg) before constructing the thread, that's why you see that you wait 5 seconds before proceeding.

Instead you should pass the thread a target function and it's args separately, without evaluating them.

Here is a fixed and minimalized version:

from time import sleep
from threading import Thread

class EMailv1():
    def __init__(self):
        self._from_address = '[email protected]'
        self._to_addresslist = []

    def buildmessage(self, **kwargs):
        message ={}
        return message

    @staticmethod
    def sendmail(message):
        sleep(5)
        print("Launched thread: I'm done sleeping!")
        return

if __name__ == "__main__":
    print('Main thread: Starting  threaded call to send V1 class')
    email = EMailv1()
    msg = email.buildmessage(a='a', b='b')
    t = Thread(target=email.sendmail, args=[msg])
    t.start()
    print("Main thread: I have created the thread, and am done.")

Output:

Main thread: Starting  threaded call to send V1 class
Main thread: I have created the thread, and am done.
Launched thread: I'm done sleeping!

With that said, I'd also suggest you to take a look at some abstractions provided in Python for doing multithreaded work. For instance, take a look at features like ThreadPoolExecutor - these are usually preferable to working directly with Threads.