Asynchronous Programming and Exceptions

ASCOM's Application Programming Interfaces (APIs) implement operations asynchronously(*). This applies to both ASCOM Alpaca and ASCOM COM of course, since they both share the same APIs.

Synchronous operations are easy to understand. You make a method call such as Dome.SlewToAzimuth(), and it returns only when the dome has successfully reached the new azimuth or throws an exception if an issue arises while the operation is underway. This may take many seconds to minutes, and all this time your program is stalled.

Asynchronous operations employ an Initiating call and a corresponding completion property. Since these relate to the same operation, either may throw an exception if an issue arises while the operation is underway. In this case, continuing our example, the SlewToAzimuth() call only initiates the process then returns "immediately" leaving your program to do other things (which don't require the dome to be at the new azimuth!). When your program needs to know that the dome has reached the new position before it can proceed, it checks the Completion property, Slewing. If it is False then the program can proceed. If Slewing is still True, then your program needs to wait at this point until Slewing does become False.

The following sections cover important details and consequences, but the key concept here is that asynchronous operations are made up of the two parts: the Method Call and the Completion Property.

All error conditions are reported by raising exceptions, and you may get exceptions from any property or method, so be prepared!

Asynchronous Method Call - Initiating an Operation

Each operation such as slewing and rotating are initiated with a method call, for example, Dome.SlewToAzimuth(). The method call returns immediately if the request is successfully started. If for any reason the device cannot successfully start the operation with the expectation of finishing it successfully, it must not return from the method call but instead it must raise an exception. From the app's perspective, if the method call returns successfully, the app can be 100% certain that the requested operation was successfully initiated with the expectation that it will complete successfully.

Asynchronous Completion Property

Later, while the operation is in progress, the app may wish to check to see if it has completed. Using the example above, the app would read the Dome.Slewing property. If it is True, then the operation is still in progress. At some point the app will need the dome to have rotated to the requested azimuth before it can move on with its other work. The app would then repeatedly check Dome.Slewing until it sees that it has changed to False. At this point the app can be 100% certain that the operation has successfully completed. Thus Dome.Slewing is the completion property for the Dome.SlewToAzimuth() method.

On the other hand, if an unanticipated problem occurs during the operation (after the method call returns) the next time the app tries to read the completion property it will not get a result back, but instead it will see a raised exception. So continuing our example, if the dome encounters a mechanical problem, or if the serial connection fails, or whatever, the next attempt to read Dome.Slewing will raise an exception. The interpretation is "I can't give you a straight answer because I have a problem: xxx error message xxxx".

Common Mistake - Timing Bugs

When an app initiates an operation, for example Dome.OpenShutter, the app rightly expects to immediately see Dome.Slewing = True, and Dome.ShutterStatus = ShutterOpening. It is a common mistake for driver developers to expose their internal state ands violate this. For example, say it takes "a little bit of time" for the shutter to start moving. If Slewing and ShutterState are the raw internal states, it may be possible for an app to see ShutterClosed and/or Slewing = False after the OpenShutter() returns. This is a bug! No dome is that fast (opening the shutter in zero time).

Other Properties

Taking this further, if an app reads a property and gets back a result, then the app can be 100% certain that the value of the property is what the device says it is. If the device has encountered an error or failure such that the property value is compromised the device must raise an exception when that property is read. For example, if an app reads Telescope.RightAscension and the mount has lost communications, clearly the mount's driver must raise an exception. But what if the mount gets mechanically stuck? It is compromised and therefore must raise an exception. The same logic applies to writing properties.

Consequences for App Developers

As an app developer, if you call an ASCOM API method, you can be 100% certain that the device successfully started the requested operation with the expectation that it could (later) complete it successfully. Later, when you see the completion property change to "complete", you can be 100% certain that the request did in fact complete successfully. Also, if you read another property and it returns normally, you can be certain that you got a truthful answer. Likewise, if you write a property and it returns normally you can be certain that the corresponding value in the device was successfully changed as you requested.

As described above, you must be prepared to receive exceptions from both method calls and property reading/writing. This indicates that something has gone wrong in the device and it is compromised. It's usually a bad idea for apps to try to diagnose and correct problems based on error responses.

Needless Polling of Completion Properties

It's en vogue to use an asynchronous operation by "spinning off a thread" in which you call the method, then repeatedly poll the completion property until it becomes "complete". Meanwhile in your main logic flow you do other things that don't require the operation to have completed. Finally you come back and look when you need to know if it has completed before you can move on. Well all that time your spun-off thread is mindlessly polling for completion before you even care! It's bad form to create traffic for the device and the net with useless polling for async completion. Would it make more sense to kick off the operation with the method call, then go on about your other business, then when you need it to be done, check to see if it has completed right there? If not, now you're stuck and can't press on anyway, so loop right there waiting for it to complete. You may end up never waiting at all.

Consequences for Device/Driver Developers

Fundamentally, your device must never lie to apps. If you can't give a correct answer or initiate a requested operation with an expectation of success, you must raise an exception. If your device becomes compromised, you must raise an exception thereafter on any request that is related to the problem. For example, if your dome shutter becomes jammed and an app calls Dome.OpenShutter() you must raise an exception when the app tries to read Slewing (the completion property). As designed, however, the ShutterStatus property has an error state so it is OK to return ShutterError. Unfortunately this won't give any additional data to the app. If the app reads Slewing, you can raise an exception containing an error message describing the problem. It never hurts to raise an exception if you have any question about the integrity of the device.

Also see Common Mistake - Timing Bugs above. As a driver developer you are responsible for avoiding this issue.


(*) There are a few historical methods which are synchronous, as well as the Connected property which is actually a property masquerading as a synchronous method. We are aware of these issues and are deprecating these in the future.