|Top Previous Next|
You may have noticed in typical synchronous usage of the runtime API that many method signatures have other methods that look very similar, yet have "Async" appended to the end of their names. These special methods are asynchronous versions of the methods they are based on, and are designed to take advantage of parallel processing to minimize unnecessary time being spent waiting for certain engines to complete before other runtime API engines begin their work.
Typically, when an application uses an engine in the standard sense without any asynchronous operations, every engine operation is blocking. Blocking in this context means that your application will wait at that line of execution until the runtime API engine completes its task, and only then moving on to the next line of your code. For trivial applications, or for applications where the operations do not take a very long time, this sort of blocking functionality is fine. Additionally, synchronous programming is very easy, and is the typical workflow for applications. It's easy to imagine that the lines of code that come later happen later, and so development is fairly easy as well.
Asynchronous programming in the context of FreeFlyer's runtime API is the concept of using multiple runtime API engines on multiple threads simultaneously to achieve development goals quicker. This may be useful if you have one Mission Plan that spends a lot of time generating a specific value A and another Mission Plan which has a number of setup routines to call before it needs value A, but then once it needs value A it has to perform calculations with it. In this case, it would be valuable to have the setup in Engine 1 and the calculation of value A in Engine 2 execute at the same time, and then have it so that Engine 1 waits when it has to get the value from Engine 2. The workflow associated with this specific example is provided in a graphic below.
The workflow of two asynchronously operating engines with positive time in the downward direction
In this example, we have what is called a sync point which is the point in time where both engines need to synchronize to each other so that a value in Engine 2 can be passed to Engine 1 before Engine 1 continues to execute, using that value. Conveniently, one way to do this, as demonstrated in the above graphic, is to have an ApiLabel set up in the Mission Plan used by Engine 1 that tells the calling application when to wait for Engine 2's output. Additionally, there are API methods specifically for setting up sync points that you can then have other engines wait on at certain points in their execution. How to code a C# application that actually functions as above is demonstrated in the below section.
There is already a sample application provided with the installation of FreeFlyer that demonstrates the usage of asynchronous programming with the runtime API, but the syntax of how to perform those asynchronous calls will be called out here for clarity. When using asynchronous calls, you create the engine as normal and then use the asynchronous versions of methods to generate a queue of actions that the engine will perform on its own thread immediately until the queue has no more items in it.
Calling the Synchronize method is one way to ensure that an engine has completed its asynchronous queue, and will block execution until the engine has finished all of the calls in its queue. Calling this method on multiple engines ensures that all engines that are being synchronized are where the programmer expects them to be at that point in time. Another approach one could use is to utilize the SetSyncPointAsync() call to literally create a sync point that another engine could wait for.
Note: Calling the Synchronize method with a timeout argument of -1 will cause it to wait indefinitely for the engine to finish executing all of its asynchronous calls.
As can be seen in the code above, the Mission Plan that generates the burn amount is kicked off first and will execute at the same time as the second Mission Plan, the one that needs the burn amount. In this case, the second Mission Plan that requires the burn amount will run all of its setup up until the ApiLabel where it needs the burn amount, at which point it will synchronize with the other Mission Plan and retrieve that data before using it.
Dynamic Asynchronous Calls
In the above example, you see how we synchronize the runtime API engines prior to having the second Mission Plan finish execution. The reason this is done is to ensure that the burnAmount AsyncData object has the value we want by the time it gets used in the second Mission Plan, and part of the reason this is necessary is because the normal asynchronous functions evaluate the values you pass when you call them, not when they actually take action. Recall that asynchronous calls ultimately build a queue of actions to take, and they run those actions sequentially until the queue finishes. It may be the case that the AsyncData value will change over the course of that queue, and you'll want to use the most recent version of that value. In such a case, you would want to take advantage of dynamic asynchronous calls. Anywhere that an asynchronous method exists, a dynamic asynchronous method exists as well, which allows for AsyncData to use the value for its data when it's actually run, not when it's called.
So, for the example provided above in syntax, you might replace the SetExpressionVariableAsync("ImpulsiveBurn1.BurnDirection", burnAmount); call with SetExpressionVariableDynamicAsync("ImpulsiveBurn1.BurnDirection", burnAmount); and then get rid of the Synchronize() calls to use the value in its most up-to-date form and save a little bit of time. It's worth noting that in such a case, you'll need to be wary of the possibility that the second Mission Plan will run too quickly and ask for the data sooner than it is ready. A more typical use-case for dynamic functionality might be when communicating across a messaging bus where the most recent value is always what you want for certain values.