libinco_32
 All Files Functions Enumerations Enumerator Macros Modules Pages
Synchronous Calling of Asynchronous Procedures

Overview

INCO procedures and GetVariable accesses can be roughly divided into two kinds:

Whether a given procedure or variable is synchronous or not is determined by its implementor, there is no way of finding out short of performing the call.

Some procedures are necessarily asynchronous, e.g. because part of their action needs to run in a different thread than the caller. However, it can often be useful to call even such procedures as if they were synchronous, i.e. the call should only return after the action is completed. The same is true for reading some variables, especially in INIX those related to graphical items (e.g. reading the text of a button), where the value of the variable must be determined in the graphical thread context. A special mechanism is provided in libinco_32 for this purpose. This mechanism is optional for in-process procedure calls within INIX and for calls to INOS. For GetVariable() (both in-process and inter-process) and for inter-process procedure calls to INIX (e.g. from Inco Explorer), it is always used, i.e. these calls are always performed synchronously. Technically this is implemented by always waiting for asynchronous calls to finish.

The basic idea is that for an asynchronous procedure, the CallProcedureEx() function returns a ticket. That ticket can then be used to either poll libinco_32 whether the asynchronous part has completed or put the calling thread to sleep and have libinco_32 wake it up on completion using the CallProcedureExWait() function. To distinguish them from the normal success (zero) or error (positive) return values of CallProcedure[Ex](), tickets are always negative. An asynchronous procedure can thus be called synchronously by doing CallProcedureExWait(..., CallProcedureEx(...)). If needed, the return value(s) of the asynchronous procedure can then be retrieved using CallProcedureExResult() or CallProcedureExResultByName() (see below). The function CallProcedureExSync() combines these three actions (call, wait, retrieve) into a convenient unit. For reading asynchronous variables, libinco_32 uses CallProcedureExResult() internally, so that GetVariable() always appears synchronous from outside.

This mechanism consists of two parts: one used by the callers of procedures to keep track of the pending calls and their tickets, one used by asynchronous procedures themselves to generate tickets and signal their completion. These parts are separate from an API point of view and all communication between them is internal. The API used by the caller is common to calls that stay within the inco_32 process (e.g. in INIX) and calls that go to external (INOS or inco_32) targets via INCOServer and consists of the functions CallProcedureExWait(), CallProcedureExResult(), CallProcedureExResultByName(), CallProcedureExSync(). The API used by asynchronous procedures in inco_32 processes is implemented in libinco_32 as well and consists of the functions CheckoutAsyncCallTicket(), ProcedureExAddResult(), ProcedureExAddAppError(), ReturnAsyncCallTicket(), ReturnAsyncCallTicketAfterCallHasFinished(). The API used by asynchronous procedures in INOS is implemented in INOS and is not documented here, see the "use case" pages linked from CallProcedureEx & friends for some examples.

To summarize, there are four combinations of calling a synchronous or asynchronous procedure using CallProcedureEx() or CallProcedureExSync():

Return Values from Asynchronous Procedures and Variable Readings

Just like a synchronous one, an asynchronous procedure or variable reader function can return a result value to its caller. In addition to the capabilities of the synchronous return, it can also return multiple results, named results, and results of other types than double (in particular strings). Because it can't reach the caller directly, it deposits the result values at libinco_32. Procedures in inco_32 processes use ProcedureExAddResult() (for ordinary results) or ProcedureExAddAppError() (for application errors) for that purpose. The caller can then pick up those result values using CallProcedureExResult() or CallProcedureExResultByName(), which include waiting until the asynchronous part is completed.

If you use the convenience function CallProcedureExSync(), picking up one result value is done automatically, and you can just use its apResult argument like you would with CallProcedure().

To avoid unbounded memory consumption, values that are never picked up will eventually be dropped by libinco_32. The values are stored in a ring buffer of fixed size, where the oldest entries are discarded if necessary to free up space for new ones. This means that callers that are interested in the results of asynchronous procedures should fetch them as soon as possible after completion of the asynchronous procedure, or they might find that no result value is available anymore (equivalent to the situation where the procedure didn't return a value in the first place).

Details

Mostly of interest to procedure implementors, here is how it works in detail for a call within an inco_32 process, in a somewhat figurative manner of speaking. Calls to external targets via INCOServer are handled slightly differently and are described under Calls to External Targets below.

The Scene

There is a transaction between four people: the caller C, Libinco_32, the procedure P, and an asynchronous part A. Libinco_32 sits at a counter and serves the requests of the other parties. She has a big roll of numbered tickets, each of which consists of several differently colored sections that can be individually torn off: a red one, a blue one, and a number of yellow ones, all bearing the ticket number.

.-----------------------------.
| re : bl        yellow       |
| d  : ue ....................|
|    :    :    :    :    :    |
|  1 :  1 :  1 :  1 :  1 :  1 |
|  6 :  6 :  6 :  6 :  6 :  6 |
|  7 :  7 :  7 :  7 :  7 :  7 |
|  3 :  3 :  3 :  3 :  3 :  3 |
|  8 :  8 :  8 :  8 :  8 :  8 |
|  0 :  0 :  0 :  0 :  0 :  0 |
'-----------------------------'

In addition, she has a tray for each caller [thread] on which she maintains a stack of tickets, and a filing cabinet where used tickets are stored, organized by their number.

A Basic Synchronous Call

Caller C arrives: "Hello, I'd like to call procedure P." [CallProcedureEx()] Libinco_32 takes a new ticket off the roll and puts it on the stack on C's tray. Then she summons P and sends him off to do his work.

At some point in his job, P decides that he needs to start an asynchronous action A. He returns to the counter and tells Libinco_32 [CheckoutAsyncCallTicket()], who takes the topmost ticket from C's tray, tears off a yellow part, and hands it to P. P passes it on to A when he sends her off to do her job.

When P returns to the counter announcing that he's done with his [the synchronous] part, Libinco_32 takes the ticket off the stack, tears off the red part, and hands it to C, who leaves with it and continues with his own business [CallProcedureEx() returns]. The rest of the ticket, containing the blue part, she files away in her cabinet.

After a while, C returns to the counter, shows his red ticket, and asks: "Has this procedure finished yet?" [CallProcedureExWait() with timeout 0] Libinco_32 looks for the ticket number in her file (and on her stacks) and sees that the ticket is missing a yellow part, so she answers "No".

After another while, C returns again, shows his ticket, and says: "I'll just wait here until the procedure is done." He sits down on a chair in front of the counter and falls asleep [CallProcedureExWait()].

When A has finished her work [the asynchronous part], she comes to Libinco_32 and hands in the yellow ticket she got from P [ReturnAsyncCallTicket()]. Libinco_32 finds the corresponding blue ticket in her file and glues the yellow part back to it. Seeing that all yellow parts are back, she wakes up C sleeping in front of the counter, who happily leaves, knowing that both the synchronous and asynchronous parts of his procedure have finished [CallProcedureExWait() returns].

Synchronous Calls Within Asynchronous Procedures

In the example above, imagine that while doing her work, A [the asynchronous part of procedure P] needs to call another asynchronous procedure Q. She orders that call at Libinco_32's counter [CallProcedureEx()] and gets a red ticket for it. But what to do when she is done with her own work, but Q isn't with his yet? She can't return her yellow ticket, because the part of her job that she commissioned Q to do isn't done yet and C would be awakened prematurely. But she can't keep it and wait for Q either, because she has other important things to do.

That's where another one of Libinco_32's services enters the picture: A returns to the counter, hands in her yellow ticket, shows the red one, and says: "Put that yellow ticket back when procedure Q, identified by this red ticket, is done." [ReturnAsyncCallTicketAfterCallHasFinished()] Instead of sticking the yellow ticket back to the blue one from which it came, Libinco_32 clips it to Q's blue ticket that she finds in her files. A can now leave, her involvement in the current transaction is over. When Q finally returns his yellow ticket, Libinco_32 sees A's yellow ticket clipped to Q's blue one, returns it to its origin on P's blue ticket, and wakes up C because all the yellow parts of P's ticket are back.

Calls to External Targets

The basic differences for calls to external targets via INCOServer are the following:


Generated by doxygen 1.8.8