Pacified
Pacified

Reputation: 175

How to use the threading module to call a function?

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

Answers (2)

Myles Hollowed
Myles Hollowed

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

gammazero
gammazero

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

Related Questions