Watches are used to query the values of properties and to receive notifications when those properties change. A StartWatch or StartWatchAsync method must be called before these properties are available and before these events are raised. Failure to start a watch will cause some property getters to throw a NotCachedException.
How Watches Work
Many classes in IceLib require a watch to be active for certain properties to be accessible or for certain events to be raised. Some of these watches are just "all or nothing", meaning either they're active or not. Other watches are "parameterized", meaning that they only watch certain items.
Several methods and properties work together to support watches for IceLib classes that can be watched. Examples are:
|StartWatching()||Starts watching the given class instance. This is an example of an "all or nothing" watch.|
|StartWatchingAsync()||Asynchronously starts watching the given class instance. This is an example of an "all or nothing" watch. See How Async Calls Work for more information.|
|StartWatching(items)||Starts watching the specified items for the given class instance. This is an example of a "parameterized" watch.|
|StartWatchingAsync(items)||Asynchronously starts watching the specified items for the given class instance. This is an example of a "parameterized" watch. See How Async Calls Work for more information.|
|bool IsWatching()||Determines whether a watch is active for the given class instance. This can be used for an "all or nothing" or a "parameterized" watch.|
|bool IsWatching(item)||Determines whether a watch is active for the specified item for the given class instance. This is used for a "parameterized" watch.|
|ChangeWatchedItems(items)||Changes the items being watched for the given class instance. The exact method name varies for different item types.|
|ChangeWatchedItemsAsync(items)||Asynchronously changes the items being watched for the given class instance. The exact method name varies for different item types. See How Async Calls Work for more information.|
|StopWatching()||Stops watching the given class instance. This can be used for an "all or nothing" or a "parameterized" watch.|
|StopWatchingAsync()||Asynchronously stops watching the given class instance. This can be used for an "all or nothing" or a "parameterized" watch. See How Async Calls Work for more information.|
For classes that have properties tied to a watch, starting a watch on a class instance not only allows the properties to be invoked for that instance, but also causes the properties that are part of the watch to be initialized with their current values for that class instance.
|Watch-based properties are available for use once the class instance's StartWatching has completed or its StartWatchingAsynccompletedCallback has been raised.|
For classes that have events tied to a watch, starting a watch on a class instance allows its related events to be raised. An example of this is a "changed event" for watched items on a class instance. Another example is an add or remove event for items in an "all or nothing" class instance watch.
|All event handlers should be added before calling StartWatching or StartWatchingAsync to avoid missing an event that is raised as part of the initial watch.|
For parameterized watches, different instances of a given class type can have different items being watched. For these instances, watch-based properties are tied to the instance on which they are being invoked.
|The items being watched on one class instance will not be available on a different class instance that is not watching those items. The following pseudocode illustrates this:
IceLibClass classInstance1 = new IceLibClass(...); IceLibClass classInstance2 = new IceLibClass(...); classInstance1.StartWatching(/* a collection causing item1 and item2 to be watched */); classInstance2.StartWatching(/* a collection causing item2 and item3 to be watched */); // These property getter succeed because classInstance1 is watching the specified items. IceLibClassItem valueOfInstance1Item1 = classInstance1.Item1; IceLibClassItem valueOfInstance1Item2 = classInstance1.Item2; // These property getter succeed because classInstance2 is watching the specified items. IceLibClassItem valueOfInstance2Item2 = classInstance2.Item2; IceLibClassItem valueOfInstance2Item3 = classInstance2.Item3; // This property getter fails because classInstance2 is not watching item1. // It will throw an ININ.IceLib.NotCachedException. IceLibClassItem valueOfInstance2Item1 = classInstance2.Item1; // Summary: The classInstance1 watch on Item1 does not affect classInstance2's Item1 property.
Watches do not carry over from disconnects. After a Session reconnects, the IC Server will not be aware of watches that were started before it disconnected; therefore, events that were being watched will no longer be invoked. Calling the IsWatching method of a watch will still return true and therefore is not an effective way of determining if a watch is no longer active due to a Session disconnect. Checking a Session’s ConnectionState should be used instead. In the event that a Session does get disconnected while there are active watches, StopWatching or StopWatchingAsync will need to be called to clean up IceLib’s cache. After a new Session is connected, new watches can then be started by calling StartWatching or StartWatchingAsync.
Calling StartWatching, StopWatching, or ChangeWatched or their async counterparts after a disconnect will have the following results:
|StartWatching()||No action is taken.|
|StopWatching()||The watch is cleaned up.||None|
|ChangeWatched()||No action is taken.|
Not Cached Exceptions
Many classes in IceLib contain properties that are only available when a watch is active (i.e. when StartWatching has been called). Generally, these properties are documented to require a call to StartWatching.
If one of these properties is used without an active watch, then usually a NotCachedException is thrown.
Interaction and Queue Watches
There are two types of watches related to Interactions, allowing different use-cases. The events that get raised are for the class on which the StartWatch was called.
- The first type is a watch for all Interactions on a given Queue. An example use of this type of watch would be as in the Interaction Client .Net Edition's "My Interactions" tab.
- This is performed using the InteractionQueue class. First by adding an event handler to InteractionQueue.InteractionChanged then using its StartWatching/StartWatchingAsync method to start watching for changes. The InteractionAdded/InteractionRemoved events will also be raised from this watch. An InteractionQueue watch only causes InteractionQueue events to be raised (not Interaction class events), but it does also allow watched attributes to be retrieved from an Interaction instance on that queue, whether an Interaction watch is active or not.
- The second type is a watch on a specific Interaction, perhaps for more advanced editing that needs to watch a broader set of attributes on the Interaction. An example of this type of watch would be as in the Interaction Client .Net Edition's dialog for viewing an individual Interaction (the Properties dialog for Calls or the Chat dialog for Chats).
- This is performed using the Interaction class. First by adding an event handler to Interaction.AttributesChanged then using its StartWatching/StartWatchingAsync method to start watching for changes. Other events such as Deallocated will also be raised from this watch. An Interaction watch only causes Interaction events to be raised (not InteractionQueue class events).
- An Interaction watch cannot be started on the "shared" Interaction class instance that is contained in InteractionQueue.InteractionChanged events, but a new Interaction instance can easily be created for the same InteractionId as the one contained in the EventArgs for the event. (This is to protect different pieces of code both handling an InteractionQueue changed event and trying to start their own separate Interaction.StartWatching watches with conflicting sets of attributes.)
Both types of Interaction-related watches can be used at the same time, on the same interactions, with the result that an Interaction instance’s available attributes will reflect the union of the attributes being watched via an InteractionQueue.StartWatching (for a queue containing the Interaction) and via an Interaction.StartWatching. However, to reiterate, the events that get raised will be on the class instance on which a given StartWatching was initiated.