Christoph
Christoph

Reputation: 27985

How do I create a lazy Future in Python (asyncio) that can be kicked off after creation?

I'm trying to wrap my head around asyncio in Python. I wrote this little programm that when invoked will first print

Server booting

Do stuff called

and then after one second

Async Thingy

That is exactly what it is supposed to do but it's not quite the way I want it yet.

Basically this mimics a Server that wants to create a PeerPool in __init__ which depends on ThingThatWeAreWaitingOn. I want to be able to create the PeerPool in __init__ and pass an Awaitable[ThingThatWeAreWaitingOn] that the PeerPool can use as soon as it is ready. Again, this seems to work just fine but the catch is, as the code stands now, we kick off the task to resolve ThingThatWeAreWaitingOn directly from within __init__ but ideally I'd like to be able to kick that off from within run().

How would I do that?

import asyncio
from typing import (
    Awaitable,
    Any
)

class ThingThatWeAreWaitingOn():
    name = "Async Thingy"

class PeerPool():

    def __init__(self, discovery: Awaitable[ThingThatWeAreWaitingOn]):
        self.awaitable_discovery = discovery

    def do_stuff(self):
        print("Do stuff called")
        self.awaitable_discovery.add_done_callback(lambda d: print(d.result().name))

class Server():

    def __init__(self):
        # This immediately kicks of the async task but all I want is to 
        # create a Future to pass that would ideally be kicked off in
        # the run() method
        self.fut_discovery = asyncio.ensure_future(self.get_discovery())
        self.peer_pool = PeerPool(self.fut_discovery)
    
    async def get_discovery(self):
        await asyncio.sleep(1)
        return ThingThatWeAreWaitingOn()

    def run(self):
        loop = asyncio.get_event_loop()
        print("Server booting")

        # Here is where I want to "kick off" the self.fut_discovery but how?
        # self.fut_discovery.kick_off_now()

        self.peer_pool.do_stuff()

        loop.run_forever()

server = Server()
server.run()

Here's a link to a runnable demo: https://repl.it/repls/PleasedHeavenlyLock

Upvotes: 3

Views: 1712

Answers (1)

Mikhail Gerasimov
Mikhail Gerasimov

Reputation: 39516

If I understand everything right, you want something like this:

class Server():    
    def __init__(self):
        self.fut_discovery = asyncio.Future()
        self.peer_pool = PeerPool(self.fut_discovery)

    async def get_discovery(self):
        await asyncio.sleep(1)
        return ThingThatWeAreWaitingOn()

    def run(self):
        loop = asyncio.get_event_loop()
        print("Server booting")

        async def discovery_done():
            res = await self.get_discovery()
            self.fut_discovery.set_result(res)
        asyncio.ensure_future(discovery_done())  # kick discovery to be done

        self.peer_pool.do_stuff()
        loop.run_forever()

You may want to rewrite code somehow to make in clearer. Right now it's not very clear what you're going to do and which part of code depends of which.

For example, awaitable_discovery name is misleading: plain awaitable not necessary has add_done_callback method. If you're planing to use this method, signature

class PeerPool():
    def __init__(self, fut_discovery: asyncio.Future):

will make more sense.

May be you should create class for discovery. You can inherit asyncio.Future or implement __await__ magic method to make it's objects future-like/awaitable.

Upvotes: 2

Related Questions