Event Driven Properties - Threading

MetaProperties provides two IObservable`1 containers which are thread safe:
  • MultiThreadedReadObservable`1
  • MultiThreadedWriteObservable`1

Just because you are reading or writing to a container from multiple threads doesn't mean that you must use one of these classes - sometimes using your own external lock is more appropriate in which case any of the standard IObservable`1 containers can be used.

The differences between the thread safe containers come down to the point at which locks are entered, and the duration for which they're held.

As an aside, while these classes are written to be generic enough for most threading scenarios where internal locking is desired, they are most commonly used with event filters to automatically dispatch the changed events back to a specific thread.

MultiThreadedReadObservable`1

The MultiThreadedReadObservable`1 has the simplest locking mechanism; it uses a ReaderWriterLockSlim to lock when reading and writing the value in the container but does not hold onto the lock when firing any of the IObservable`1 events.

The advantage of this is that the actions within the locks don't have side effects, making them safe in the sense that you cannot deadlock while using this container.

However the disadvantage is that if you were to write to the container from multiple threads the order in which you would process the events is not guaranteed.

For example take a MultiThreadedReadObservable<int> whose current value is 0. Thread A and thread B try to change it's value simultaniously to 1 and 2 respectively. Lets say thread A enters the lock first and changes the value from 0 to 1. Then thread A leaves the lock and before it manages to fire any events thread B enters the lock changing the value from 1 to 2. Thread B then fires its events, followed by thread A.

In this case you would first receive a ValueChanged event with an old value of 1 and a new value of 2. Immediately after you would receive another ValueChanged event with an old value of 0 and a new value of 1.

This can obviously be dangerous if your application is relying on receiving these events in order.

Finally, the check to see if the value has changed also occurs outside the lock in this class. This means that if two threads simultaniously try to set the value from 0 to 1 they may both succeed and raise ValueChanged events with the same old and new values, rather than one ValueChanged and one ValueSet event.

The only way to guarantee the order and type of events with this container (without using an external lock, in which case you might as well use a standard Observable`1) is if you always write to it from the same thread. Under these conditions you can still safely read the contained value from any thread, hence the container is called a '''MultiThreadedRead'''Observable`1.

MultiThreadedWriteObservable`1

The MultiThreadedWriteObservable`1 class addresses the two concurrency issues with the previous container at the cost of greater complexity.

It has two modes of operation:
  • RaiseEventsInsideLock
  • RaiseEventsOutsideLockWherePossible

RaiseEventsOutsideLockWherePossible

The RaiseEventsOutsideLockWherePossible mode differs from the MultiThreadedReadObservable`1 container by locking just before the check to see if the value of the container has changed when setting a new value.

Because of this, if two threads set the value simultaniously from 0 to 1 you are guaranteed to only get one ValueChanged event and one ValueSet event.

All events are still fired outside of the lock with the exception of the PropertyChanging event which must be fired within the lock between checking if the value has changed and actually checking the value.

Ordering is therefore still not guaranteed for the other events.

RaiseEventsInsideLock

The RaiseEventsInsideLock model differs from the MultiThreadedReadObservable`1 container by locking around all the code when setting a value.

So firstly if two threads set the value simultaniously from 0 to 1 you are guaranteed to only get one ValueChanged event and one ValueSet event.

Secondly, the order of events is guaranteed to be correct.

However, '''all events are fired from within the lock''' which means you must be very careful not to cause deadlocks when handling these events.

One common use of this mode is to use it along with a DispatchingEventFilter which asynchronously dispatches all events back to a specific thread. This still guarantees the event ordering, but you will no longer be within the lock when handling the events.

Last edited Mar 9, 2010 at 1:14 PM by jamesthurley, version 2

Comments

No comments yet.