Skip to main content
Skip table of contents

Asynchronous Method Invocation (AMI) in Python

Asynchronous Method Invocation (AMI) is the term used to describe the client-side support for the asynchronous programming model. AMI supports both oneway and twoway requests, but unlike their synchronous counterparts, AMI requests never block the calling thread. When a client issues an AMI request, the Ice runtime hands the message off to the local transport buffer or, if the buffer is currently full, queues the request for later delivery. The application can then continue its activities and poll or wait for completion of the invocation, or receive a callback when the invocation completes.

AMI is transparent to the server: there is no way for the server to tell whether a client sent a request synchronously or asynchronously.

We recommend using asyncio together with AMI in new Python applications.

Asynchronous API

Consider the following Slice definition:

SLICE
module VisitorCenter
{
    interface Greeter
    {
        string greet(string name);
    }
}

slice2py generates the following asynchronous proxy method:

PY
def greetAsync(
  self, name: str, context: dict[str, str] | None = None) -> Awaitable[str]:
  ...

As you can see, the greetoperation generates a greetAsync method that accepts an optional per-invocation request context.

The greetAsync sends (or queues) an invocation of greet. This method does not block the calling thread. It returns an awaitable object that you typically await.

For example:

PY
# On asyncion event loop thread.
async with Ice.initialize(
  sys.argv, eventLoop=asyncio.get_running_loop()) as communicator:
    greeter = VisitorCenter.GreeterPrx(
      communicator, "greeter:tcp -h localhost -p 4061")
    # Invoke the greetAsync method and await the result in the event loop thread.
    greeting = await greeter.greetAsync(getpass.getuser())

Asynchronous Exception Semantics

If an asynchronous invocation throws an exception, the exception can be obtained from the awaitable.

The exception is provided by the awaitable, even if the actual error condition for the exception was encountered during the call to the Async method ("on the way out"). The advantage of this behavior is that all exception handling is located with the code that awaits the result.

There are two exceptions to this rule:

  • if you destroy the communicator and then make an asynchronous invocation, the Async method throws CommunicatorDestroyedException directly.

  • a call to an Async method can throw TwowayOnlyException. An Async method throws this exception if you call an operation that has a return value or out-parameters on a oneway proxy.

This distinction is only relevant if you are using the Future APIs directly, when using await you handle exceptions throw synchronously and asynchronously with the same except block.

Awaitable Objects

asyncio.Future, Ice.Future, and future types created by a custom event loop adapter are all awaitable objects—meaning they can be used as the target of the await keyword.

The type of awaitable object returned by Ice’s asynchronous APIs and generated asynchronous methods depends on the configured event loop adapter:

  • Default (no event loop adapter configured)
    Ice returns Ice.Future objects, including Ice.InvocationFuture for invocations.

  • With an asyncio event loop
    Ice returns asyncio.Future objects when the communicator is initialized with an asyncio event loop.

  • With a custom event loop adapter
    Ice returns custom awaitable objects provided by the application’s EventLoopAdapter implementation.

asyncio Integration

Ice 3.8 provides seamless integration with Python’s asyncio library.

If you supply an asyncio event loop during communicator initialization using the eventLoop parameter of Ice.initialize, asynchronous operations will return standard asyncio.Future objects instead of Ice’s own future types. This allows you to await asynchronous invocations directly within the asyncio event loop.

PY
async with Ice.initialize(
  sys.argv,
  eventLoop=asyncio.get_running_loop()) as communicator:
    greeter = VisitorCenter.GreeterPrx(
      communicator,
      "greeter:tcp -h localhost -p 4061")
      
    # Send a request to the remote object and get the response.
    greeting = await greeter.greetAsync(getpass.getuser())

The same mechanism can be used to integrate Ice with other asynchronous event loop frameworks. Instead of passing an asyncio loop directly, you must implement the Ice.EventLoopAdapter abstract base class for your event loop of choice and provide it during communicator initialization via the InitializationData.eventLoopAdapter member.

Event loop restrictions

You can only await a future from the event loop that created it:

  • Ice.Future and Ice.InvocationFuture
    These are tied to the Ice thread pool. You cannot normally await them from a regular Python thread or from within asyncio.

    Example (❌ does not work):

    PY
    with Ice.initialize(sys.argv) as communicator:
      greeter = VisitorCenter.GreeterPrx(
        communicator,
        "greeter:tcp -h localhost -p 4061")
        # Will fail because the returned Ice.InvocationFuture
        # cannot be awaited from a regular Python thread
        greeting = await greeter.greetAsync(getpass.getuser())

    However, you can await an Ice.InvocationFuture from inside an asynchronous dispatch (AMD), since these coroutines run on the Ice thread pool:

    Example (✅ works inside AMD with Ice futures):

    PY
    async def greet(self, name: str, current: Ice.Current) -> str:
      # Using await here is fine because the async dispatch
      # runs in the Ice thread pool
      return await self.target.greetAsync(name)
  • asyncio.Future
    These belong to the asyncio event loop supplied during communicator initialization.
    Since asynchronous dispatches also run in this loop, it is safe to await asyncio.Future objects inside an asyncio-based dispatch.

    Example (✅ works in asyncio client):

    PY
    async with Ice.initialize(
      sys.argv,
      eventLoop=asyncio.get_running_loop()) as communicator:
      
      greeter = VisitorCenter.GreeterPrx(
        communicator, "greeter:tcp -h localhost -p 4061")
    
      # Fine: we are running in the asyncio event loop
      # and greetAsync returns an asyncio.Future
      greeting = await greeter.greetAsync(getpass.getuser())

    Example (✅ works in asyncio-based AMD):

    PY
    async def greet(self, name: str, current: Ice.Current) -> str:
      # Using await here is also fine because the async dispatch
      # runs in the configured event loop (asyncio in this case)
      return await self.target.greetAsync(name)

Asynchronous Oneway Invocations

You can invoke operations via oneway proxies asynchronously, provided the operation has void return type, does not have any out-parameters, and does not raise user exceptions. If you call an asynchronous proxy method on a oneway proxy for an operation that returns values or raises a user exception, the method throws TwowayOnlyException.

Oneway invocation completes as soon as the request is successfully written to the client-side transport. Exceptions are only reported if an error occurs before the request is successfully written.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.