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)
0 comments:
Post a Comment