How to create multiple add_callback in tornado?

7.1k views Asked by At

I'm trying to add multiple callbacks in tornado main loop. But when I run this code:

def task(num):
    print 'task %s' % num

if __name__ == '__main__':
    for i in range(1,5):
        tornado.ioloop.IOLoop.instance().add_callback(callback=lambda: task(num=i))
    tornado.ioloop.IOLoop.instance().start()

I get output 5 times: 'task 5', not task 1.. task 5. When I change main like that:

tornado.ioloop.IOLoop.instance().add_callback(callback=lambda: task(1))
tornado.ioloop.IOLoop.instance().add_callback(callback=lambda: task(2))
tornado.ioloop.IOLoop.instance().add_callback(callback=lambda: task(3))
tornado.ioloop.IOLoop.instance().add_callback(callback=lambda: task(4))

everything works fine (I get task1-task5 in output). What am I doing wrong in the first case?

2

There are 2 answers

1
Elephant On BEST ANSWER

Maybe there is the same problem like in JS? look at this answer: JavaScript closure inside loops – simple practical example

"the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function."

try simple test:

def task(num):
    print 'task %s' % num

def create_task(num):
    tornado.ioloop.IOLoop.instance().add_callback(callback=lambda: task(num))

if __name__ == '__main__':
    for i in range(1,5):
        create_task(i)
    tornado.ioloop.IOLoop.instance().start()
0
Anton On

Wrap your lambda into functools.partial() function which solves "pointer issue" IOLoop.instance().add_callback(callback=functools.partial(task, i*10))

tl;dr;

Here is an example with the "pointer issue" BUG:

def task(num):
    print(num)

for i in range(1, 6):
    print ("poop {0}".format(i))
    IOLoop.instance().add_callback(callback=lambda: task(i))
IOLoop.instance().start()

at the code example above i will be a memory pointer to the value range() currently iterating on. It is changing from 1 to 2 then to 3, 4, 5.

Tornado's add_callback() asynchronous function returns control to the main program immediately making all the 5 calls to task() function execute almost at the same time. And at this point i is already got the value of 5.

To solve this issue quickly use funtools.partial function instead of lambda and research the difference ;)

Here is BUG-free example:

IOLoop.instance().add_callback(callback=functools.partial(task, i*10))