Search code examples
tornado

Tornado Execution Order Spawn callback


If I have something like the following:

 @tornado.gen.coroutine
 def first(x):
    # 
    # do stuff


    for i in I:
       tornado.ioloop.IOLoop.current().spawn_callback(func,i)

    tornado.ioloop.IOLoop.current().spawn_callback(func2,z)

 yield first(xxx)

Can I be guaranteed that all the spawned functions in the for loop will run before the last spawning of callback to func2()?


Solution

  • No, in fact you're guaranteed that all the functions are spawned before any of them starts running, because first does not yield between spawning func and spawning func2. You can verify this yourself by testing your code:

    from tornado import gen, ioloop
    
    @gen.coroutine
    def func():
        print('func started')
        yield gen.moment
        print('func done')
    
    
    @gen.coroutine
    def func2():
        print('func2 started')
        yield gen.moment
        print('func2 done')
    
    
    @gen.coroutine
    def first():
        for i in range(2):
            ioloop.IOLoop.current().spawn_callback(func)
    
        ioloop.IOLoop.current().spawn_callback(func2)
        yield gen.sleep(1)
    
    ioloop.IOLoop.current().run_sync(first)
    

    It prints:

    func started
    func started
    func2 started
    func done
    func done
    func2 done
    

    See, func2 begins before the coroutines running func complete.

    To accomplish what you want:

    @gen.coroutine
    def first():
        yield [func() for i in range(2)]
        ioloop.IOLoop.current().spawn_callback(func2)
    

    This prints:

    func started
    func started
    func done
    func done
    func2 started
    func2 done
    

    If you want first to wait for func2 to finish before it exits, then:

    @gen.coroutine
    def first():
        yield [func() for i in range(2)]
        yield func2()
    

    For more info on calling coroutines from coroutines, see my Refactoring Tornado Coroutines.