zeroc-ice / ice-demos

Sample programs for Ice
https://zeroc.com
GNU General Public License v2.0
325 stars 217 forks source link

Simplify async demos to use asyncio #126

Closed bernardnormier closed 3 years ago

bernardnormier commented 3 years ago

This PR simplifies the async and asyncInvocation demos by switching to await and asyncio.

pepone commented 3 years ago

If you merge this into 3.7 don't forget to cherry-pick to 3.7.5-rc too, or just update the PR to merge in 3.7.5-rc

bernardnormier commented 3 years ago

Should we consider using a dispatch instead of wrap_future? Futures returned by Ice async calls would get completed through the dispatcher which would call loop.call_soon_threadsafe to ensure that the continuation of the future is ran on event loop.

We could also consider adding a loop parameter to the communicator initialization (if set this would setup a dispatcher to run calls on the loop).

Your comment is not clear to me.

bernardnormier commented 3 years ago

If you merge this into 3.7 don't forget to cherry-pick to 3.7.5-rc too, or just update the PR to merge in 3.7.5-rc

Merged to 3.7.5-rc.

bentoi commented 3 years ago

My idea was to do the same as the C# async demo... something along these lines:

async def main():

    # Ice.initialize returns an initialized Ice communicator; the communicator is destroyed once it goes out of scope.
    with Ice.initialize(sys.argv, "config.client") as communicator:

        # The communicator initialization removes all Ice-related arguments from sys.argv
        if len(sys.argv) > 1:
            print(sys.argv[0] + ": too many arguments")
            return 1

        try:
            hello = Demo.HelloPrx.uncheckedCast(communicator.propertyToProxy('Hello.Proxy'))
        except Ice.Exception as ex:
            print("invalid proxy:", ex)
            return 1

        menu()

        while True:
            sys.stdout.write("==> ")
            sys.stdout.flush()
            c = await asyncio.get_event_loop().run_in_executor(None, sys.stdin.readline)
            c = c.strip()

            if c == 'i':
                await Ice.wrap_future(hello.sayHelloAsync(0))
            elif c == 'd':
                asyncio.create_task(slowSayHello(hello))
            elif c == 's':
                await Ice.wrap_future(hello.shutdownAsync())
            elif c == 'x':
                return 0
            elif c == '?':
                menu()
            else:
                print(f"unknown command {c!r}")
                menu()

async def slowSayHello(hello):
    try:
        await Ice.wrap_future(hello.sayHelloAsync(5000))
        print("slow hello completed")
    except Ice.Exception as ex:
        print("task exception:", ex)
    except RuntimeError as ex:
        print("task runtime error:", ex)

While sayHelloAsync(0) and shutdown() won't run in parallel, I don't think that's a big deal, the demo is a lot simpler like this.

Another step could be to get rid of the Ice.wrap_future calls by using a dispatcher that ensures that AMI continuations are ran from the event loop. I tried this but for some reasons it doesn't work, I'm getting a RuntimeError: Task got bad yield: <Ice.InvocationFuture object at 0x10a374fd0> from the yield in IceFuture.__await__ (defined in IceFuture.py).