Reputation: 175
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.
Upvotes: 2
Views: 255
Reputation: 546
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)
Upvotes: 1
Reputation: 953
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
Upvotes: 3