82

I was trying the following code:

import asyncio

@asyncio.coroutine
def func_normal():
        print("A")
        yield from asyncio.sleep(5)
        print("B")
        return 'saad'

@asyncio.coroutine
def func_infinite():
    i = 0
    while i<10:
        print("--"+str(i))
        i = i+1
    return('saad2')

loop = asyncio.get_event_loop()

tasks = [
    asyncio.async(func_normal()),
    asyncio.async(func_infinite())]

loop.run_until_complete(asyncio.wait(tasks))
loop.close()

I can't figure out how to get values in variables from these functions. I can't do this:

asyncio.async(a = func_infinite())

as this would make this a keyword argument. How do I go about accomplishing this?

dano
  • 91,354
  • 19
  • 222
  • 219
Saad Aleem
  • 1,709
  • 1
  • 12
  • 18

4 Answers4

82

The coroutines work as is. Just use the returned value from loop.run_until_complete() and call asyncio.gather() to collect multiple results:

#!/usr/bin/env python3
import asyncio

@asyncio.coroutine
def func_normal():
    print('A')
    yield from asyncio.sleep(5)
    print('B')
    return 'saad'

@asyncio.coroutine
def func_infinite():
    for i in range(10):
        print("--%d" % i)
    return 'saad2'

loop = asyncio.get_event_loop()
tasks = func_normal(), func_infinite()
a, b = loop.run_until_complete(asyncio.gather(*tasks))
print("func_normal()={a}, func_infinite()={b}".format(**vars()))
loop.close()

Output

--0
--1
--2
--3
--4
--5
--6
--7
--8
--9
A
B
func_normal()=saad, func_infinite()=saad2
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • This works. I did run functions for my django app concurrently using this approach but I didn't see a gain in performance. It takes the same time to process. – Saad Aleem Sep 09 '15 at 12:19
  • @AleemSaadullah: `asyncio` allows you to run 10 `func_normal()` tasks in 5 seconds instead of 50 seconds. All tasks are executed in *the same thread* unless you use an executor to offload some work to a thread/process pool. I'm not familiar with running django using asyncio (if it is even possible or whether it makes any sense without substancial restructuring in django). – jfs Sep 09 '15 at 12:30
  • Yes, the code works for functions such as the ones defined here. But I guess, the mongodb ORM(mongoengine) is supposed to support asynchronous queries. I think that is the problem here. – Saad Aleem Sep 09 '15 at 18:56
  • The functions in my actual project primarily build queries and execute them. – Saad Aleem Sep 09 '15 at 19:32
17

loop.run_until_complete returns the value returned by the function you pass into it. So, it will return the output of asyncio.wait:

import asyncio

@asyncio.coroutine
def func_normal():
    print("A")
    yield from asyncio.sleep(5)
    print("B")
    return 'saad'

@asyncio.coroutine
def func_infinite():
    i = 0
    while i<10:
        print("--"+str(i))
        i = i+1
    return('saad2')

loop = asyncio.get_event_loop()

tasks = [
    asyncio.async(func_normal()),
    asyncio.async(func_infinite())]

done, _ = loop.run_until_complete(asyncio.wait(tasks))
for fut in done:
    print("return value is {}".format(fut.result()))
loop.close()

Output:

A
--0
--1
--2
--3
--4
--5
--6
--7
--8
--9
B
return value is saad2
return value is saad

You can also access the results directly from the tasks array:

print(tasks[0].result())
print(tasks[1].result())
dano
  • 91,354
  • 19
  • 222
  • 219
5

If you want to use any value returned by coroutine as soon as coroutine ends you can pass future object into the coro and update this future by computed value. As soon as future is updated it passes its future.result() to the callback function which is bound with given future. See code below:

import asyncio


async def func_normal(future):
    print("A")
    await asyncio.sleep(5)
    print("B")
    # return 'saad'
    future.set_result('saad')


async def func_infinite(future):
    i = 0
    while i<10:
        print("--"+str(i))
        i = i+1
    # return('saad2')
    future.set_result('saad2')

def got_result(future):
    print(future.result())

loop = asyncio.get_event_loop()
future1 = asyncio.Future()
future2 = asyncio.Future()

future1.add_done_callback(got_result)
future2.add_done_callback(got_result)

# Coros are automatically wrapped in Tasks by asyncio.wait() 
coros = [
    func_normal(future1),
    func_infinite(future2)]

loop.run_until_complete(asyncio.wait(coros))
loop.close()

The callback function is called with a single argument - the future object which it is bound with. If you need to pass more arguments into the callback use partial from functools package:

future1.add_done_callback(functools.partial(print, "future:", argin))

will call

print("future:", argin)
Petr Javorik
  • 1,695
  • 19
  • 25
1

I found a solution that I like better:

    loop = asyncio.new_event_loop()
    task = loop.create_task(awaitable(*args, **kwargs))

    loop.run_until_complete(task)

    return task.result()
Sergio Pulgarin
  • 869
  • 8
  • 20