Friday, November 10, 2017

How to use the threading module to call a function?

Leave a Comment

Or in other words, how to create a time delayed function?
I have a python bot that is supposed to send notifications to user's followers upon the usage of certain commands.

For example , if Tim runs the command ' >follow Tom ', all of Tim's followers will be notified in PM's that he followed Tom , and Tom will be notified that Tim followed him.

I have tested this function with a large amount of followers , and the bot remains stable and avoids being kicked from the server , i'm guessing because the for loop adds a delay to each message sent to each follower.

The problem I have is if two user's were to simultaneously run a command that warrant's a notification. The bot gets kicked offline immediately. So what I need is to add an artificial delay before the notification function is run. Time.sleep() , does not work. all it does it freeze the entire program , and hold every command in a queue. (If two user's ran >follow , it would sleep for 2 seconds , and just run both their commands after the delay)

I'm trying to use the threading module in order to replace time.sleep(). My notification function is the following.

#message is the message to be sent, var is the username to use def notify(message,var):       #SQL connect        dbconfig = read_db_config()       conn = MySQLConnection(**dbconfig)       cursor = conn.cursor()       #choose all of user's followers       cursor.execute('select username from users where notifications=0 and username IN (select follower from followers where followed like "{}")'.format(var))       results = cursor.fetchall()       #for each , send a PM       for result in results:         self.pm.message(ch.User(str(result[0])), message)       conn.close()   

So how would I use threading to do this? I've tried a couple ways , but let's just go with the worst one.

def example(_):     username = 'bob'     # _ is equal to args     a = notify("{} is now following {}.".format(username,_),username)     c =threading.Timer(2,a)     c.start() 

This will throw a Nonetype error in response.

Exception in thread Thread-1: Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner self.run() File "/usr/lib/python2.7/threading.py", line 1082, in run self.function(*self.args, **self.kwargs) TypeError: 'NoneType' object is not callable

Note: I think this method will work , there will be a lot of users using the bot at once, so until it breaks this seems like a fix.

2 Answers

Answers 1

Here is some code that may help. Notice the change to how notify handles the results.

import threading import Queue  def notifier(nq):     # Read from queue until None is put on queue.     while True:         t = nq.get()         try:             if t is None:                 break             func, args = t             func(*args)             time.sleep(2) # wait 2 seconds before sending another notice         finally:             nq.task_done()   # message is the message to be sent, var is the username to use, nq is the # queue to put notification on. def notify(message, var, nq):     #SQL connect     dbconfig = read_db_config()     conn = MySQLConnection(**dbconfig)     cursor = conn.cursor()     #choose all of user's followers     cursor.execute('select username from users where notifications=0 and username IN (select follower from followers where followed like "{}")'.format(var))     results = cursor.fetchall()     #for each , send a PM     for result in results:         # Put the function to call and its args on the queue.         args = (ch.User(str(result[0])), message)         nq.put((self.pm.message, args))     conn.close()   if __name__ == '__main__':     # Start a thread to read from the queue.     nq = Queue.Queue()     th = threading.Thread(target=notifier, args=(nq,))     th.daemon = True     th.start()     # Run bot code     # ...     #     nq.put(None)     nq.join() # block until all tasks are done 

Answers 2

I would try using a threading lock like the class I wrote below.

This will cause only one thread to be able to send PM's at any given time.

class NotifyUsers():     def __init__(self, *args, **kwargs):         self.notify_lock = threading.Lock()         self.dbconfig = read_db_config()         self.conn = MySQLConnection(**dbconfig)         self.cursor = self.conn.cursor()      def notify_lock_wrapper(self, message, var):         self.notify_lock.acquire()         try:             self._notify(message, var)         except:             # Error handling here             pass         finally:             self.notify_lock.release()      def _notify(self, message, var):         #choose all of user's followers         self.cursor.execute('select username from users where notifications=0 and username IN (select follower from followers where followed like "{}")'.format(var))         results = self.cursor.fetchall()          #for each, send a PM         for result in results:             self.pm.message(ch.User(str(result[0])), message) 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment