In this section, we describe the Ice implementation of AMI and how to use it. We begin by discussing a way to (partially) simulate AMI using oneway invocations. This is not a technique that we recommend, but it is an informative exercise that highlights the benefits of AMI and illustrates how it works. Next, we explain the AMI mapping and illustrate its use with examples.
As we discussed at the beginning of the chapter, synchronous invocations are not appropriate for certain types of applications. For example, an application with a graphical user interface typically must avoid blocking the window system’s event dispatch thread because blocking makes the application unresponsive to user commands. In this situation, making a synchronous remote invocation is asking for trouble.
The application could attempt to avoid this situation using oneway invocations (see
Section 32.14), which by definition cannot return a value or have any
out parameters. Since the Ice run time does not expect a reply, the invocation blocks only as long as it takes to establish a connection (if necessary), marshal the request, and copy the message into the local transport buffer. However, these network activities may still block. Furthermore, the use of oneway invocations may require unacceptable changes to the interface definitions. For example, a twoway invocation that returns results or raises user exceptions must be converted into at least two operations: one for the client to invoke with oneway semantics that contains only in parameters, and one (or more) for the server to invoke to notify the client of the results.
interface I {
int op(string s, out long l);
};
In its current form, the operation op is not suitable for a oneway invocation because it has an
out parameter and a non-
void return type. In order to accommodate a oneway invocation of
op, we can change the Slice definitions as shown below:
interface ICallback {
void opResults(int result, long l);
};
interface I {
void op(ICallback* cb, string s);
};
•
We added interface ICallback, containing an operation
opResults whose arguments represent the results of the original twoway operation. The server invokes this operation to notify the client of the completion of the operation.
•
We modified I::op to be compliant with oneway semantics: it now has a
void return type, and takes only in parameters.
As you can see, we have made significant changes to our interface definitions to accommodate the implementation requirements of the client. One ramification of these changes is that the client must now also be a server, because it must create an instance of
ICallback and register it with an object adapter in order to receive notifications of completed operations.
A more severe ramification, however, is the impact these changes have on the type system, and therefore on the server. Whether a client invokes an operation synchronously or asynchronously should be irrelevant to the server; this is an artifact of behavior that should have no impact on the type system. By changing the type system as shown above, we have tightly coupled the server to the client, and eliminated the ability for
op to be invoked synchronously.
To make matters even worse, consider what would happen if op could raise user exceptions. In this case,
ICallback would have to be expanded with additional operations that allow the server to notify the client of the occurrence of each exception. Since exceptions cannot be used as parameter or member types in Slice, this quickly becomes a difficult endeavor, and the results are likely to be equally difficult to use.
At this point, you will hopefully agree that this technique is flawed in many ways, so why do we bother describing it in such detail? The reason is that the Ice implementation of AMI uses a strategy similar to the one described above, with several important differences:
AMI operations have the same semantics in all of the language mappings that support asynchronous invocations. This section provides a language-independent introduction to the AMI model.
Annotating a Slice operation with the AMI metadata tag does not prevent an application from invoking that operation using the traditional synchronous model. Rather, the presence of the metadata extends the proxy with an asynchronous version of the operation, so that invocations can be made using either model.
The asynchronous operation never blocks the calling thread. If the message cannot be accepted into the local transport buffer without blocking, the Ice run time queues the request and immediately returns control to the calling thread.
The parameters of the asynchronous operation are modified similar to the example from
page 2034: the first argument is a callback object (described below), followed by any
in parameters in the order of declaration. The operation’s return value and
out parameters, if any, are passed to the callback object when the response is received.
The asynchronous operation only raises CommunicatorDestroyedException directly; all other exceptions are reported to the callback object. See
page 2053 for more information on error handling.
Finally, the return value of the asynchronous operation is a boolean that indicates whether the Ice run time was able to send the request synchronously; that is, whether the entire message was immediately accepted by the local transport buffer. An application can use this value to implement flow control (see
page 2046).
The asynchronous operation requires the application to supply a callback object as the first argument. This object is an instance of an application-defined class; in strongly-typed languages this class must inherit from a superclass generated by the Slice compiler. In contrast to the example on
page 2034, the callback object is a purely local object that is invoked by the Ice run time in the client, and not by the remote server.
The Ice run time always invokes methods of the callback object from a thread in an Ice thread pool, and never from the thread that is invoking the asynchronous operation. Exceptions raised by a callback object are ignored but may cause the Ice run time to log a warning message (see the description of
Ice.Warn.AMICallback in
Appendix D).
The Ice run time invokes ice_response to supply the results of a successful twoway invocation; this method is not invoked for oneway invocations. The arguments to
ice_response consist of the return value (if the operation returns a non-
void type) followed by any
out parameters in the order of declaration.
For an asynchronous invocation, the Ice run time calls ice_response or
ice_exception, but not both. It is possible for one of these methods to be called before control returns to the thread that is invoking the operation.
The ice_sent method is invoked when the entire message has been passed to the local transport buffer. The Ice run time does not invoke
ice_sent if the asynchronous operation returned true to indicate that the message was sent synchronously. An application must make no assumptions about the order of invocations on a callback object;
ice_sent can be called before, after, or concurrently with
ice_response or
ice_exception. Refer to
page 2046 for more information about the purpose of this method.