Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call an async function in an normal function

I'm pretty new to the asyncio for python 3.6

So the thing is I have a class and I want to init some property in there. And one of the property is the return value from an async function.

What's the best practice to do this?

  1. Call event_loop one time in the init function to get the return value?

  2. Make the __init__ function async? and run it in the event loop?

Cheers!

UPDATE AGAIN:

Following is my code:

import asyncio
import aioredis
from datetime import datetime

class C:
    def __init__(self):
        self.a = 1
        self.b = 2
        self.r = None
        asyncio.get_event_loop().run_until_complete(self._async_init())

    async def _async_init(self):
        # this is the property I want, which returns from an async function
        self.r = await aioredis.create_redis('redis://localhost:6379')

    async def heart_beat(self):
        while True:
            await self.r.publish('test_channel', datetime.now().__str__())
            await asyncio.sleep(10)

    def run(self):
        asyncio.get_event_loop().run_until_complete(self.heart_beat())

c=C()
c.run()
like image 268
qichao_he Avatar asked Mar 26 '18 14:03

qichao_he


People also ask

Can we use async await in normal function?

The await operator is used to wait for a Promise and get its fulfillment value. It can only be used inside an async function or at the top level of a module.

Can you call an async function?

Handling errors in async functions is very easy. Promises have the . catch() method for handling rejected promises, and since async functions just return a promise, you can simply call the function, and append a . catch() method to the end.

Can I call async function inside async function?

async and await Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.

Can I call async method without await?

The current method calls an async method that returns a Task or a Task<TResult> and doesn't apply the Await operator to the result. The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete.


1 Answers

Call event_loop one time in the init function to get the return value?

If you spin the event loop during __init__, you won't be able to instantiate C while the event loop is running; asyncio event loops don't nest.

[EDIT: After the second update to the question, it appears that the event loop gets run by a non-static method C.run, so run_until_complete in __init__ will work with the code as written. But that design is limited - for example it doesn't allow constructing another instance of C or of a class like C while the event loop is running.]

Make the __init__ function async? and run it in the event loop?

__init__ cannot be made async without resorting to very ugly hacks. Python's __init__ operates by side effect and must return None, whereas an async def function returns a coroutine object.

To make this work, you have several options:

Async C factory

Create an async function that returns C instances, such as:

async def make_c():
    c = C()
    await c._async_init()
    return c

Such a function can be async without problems, and can await as needed. If you prefer static methods to functions, or if you feel uncomfortable accessing private methods from a function not defined in the class, you can replace make_c() with a C.create().

Async C.r field

You can make the r property asynchronous, simply by storing a Future inside of it:

class C:
    def __init__(self):
        self.a = 1
        self.b = 2
        loop = asyncio.get_event_loop()
        # note: no `await` here: `r` holds an asyncio Task which will
        # be awaited (and its value accessed when ready) as `await c.r`
        self.r = loop.create_task(aioredis.create_redis('redis://localhost:6379'))

This will require every use of c.r to be spelled as await c.r. Whether that is acceptable (or even beneficial) will depend on where and how often it is used elsewhere in the program.

Async C constructor

Although __init__ cannot be made async, this limitation doesn't apply to its low-level cousin __new__. T.__new__ may return any object, including one that is not even an instance of T, a fact we can use to allow it to return a coroutine object:

class C:
    async def __new__(cls):
        self = super().__new__(cls)
        self.a = 1
        self.b = 2
        self.r = await aioredis.create_redis('redis://localhost:6379')
        return self

# usage from another coroutine
async def main():
    c = await C()

# usage from outside the event loop
c = loop.run_until_complete(C())

This last approach is something I wouldn't recommend for production code, unless you have a really good reason to use it.

  • It is an abuse of the constructor mechanism, since it defines a C.__new__ constructor that doesn't bother to return a C instance;
  • Python will notice the above and will refuse to invoke C.__init__ even if you define or inherit its (sync) implementation;
  • Using await C() looks very non-idiomatic, even (or especially) to someone used to asyncio.
like image 145
user4815162342 Avatar answered Sep 18 '22 13:09

user4815162342