Freigeben über


Handling Events with Reactive Extensions

If you've had a chance to play around with Silverlight for Windows Phone, you've likely realized the importance of asynchronous programming and event handling.  Pushing tasks off thread keeps your UI responsive and allows users to interact with your application while you perform important tasks in the background.

While standard event handler patterns work well for simple scenarios, you may have at some point wished you had more flexibility in the way you handle events.  Maybe you'd like to filter the events firing at you and only respond to a subset of those events based on criteria that you specify.  Maybe you've listened for a stream of web service responses only to find that a user action has invalidated that stream, and you'd like to ignore any stale responses that might come back and just start listening for new ones.

You might typically solve these problems by adding Boolean flags and other state handling artifacts into your code to manage all the state required to describe your scenarios.  Though achievable this way, these solutions add bloat to your code and distract someone reading the code from its primary objective.

Over my experience building Windows Phone applications, I've encountered several common scenarios like the ones above that I was able to implement elegantly using the Reactive Extensions for .NET.  These extensions support a different model for handling events that has the flexibility and power to handle the above scenarios elegantly without requiring much effort.  The resulting solutions succinctly describe their desired behavior and at the same time avoid some of the common pitfalls associated with the standard event handler patterns.

Deregistering Event Handlers

Let me start with a simple example of how Reactive Extensions handles a common pitfall with the standard event handler patterns.

Have you ever forgotten to deregister an event handler?  If you have, you may have experienced the unpredictable behavior caused by event handlers firing an unexpected number of times.

With Reactive Extensions, the deregistering of event handlers happens automatically once the behavior you describe is accomplished.  If you're only interested in one instance of an event, Reactive Extensions will deregister that event handler for you once the event is raised.  This is accomplished with code like the following.

 Observable.FromEvent(
    (EventHandler<EventArgs> ev) => new EventHandler(ev),
    ev => App.Initialized += ev,
    ev => App.Initialized -= ev).
    Take(1).
    Subscribe(
    (args) =>
    {
        SearchInProgress = false;
    });

Since you specify that you are only interested in one Initialized event with Take(1), the Reactive Extensions will deregister the event handler for you once it receives that one instance of the event.  The discipline of deregistering event handlers is enforced by the declaration of the behavior.

Another advantage of this model is that the deregistering of the event handler is grouped along with the qualifiers for the event and the definition of the event handler, making a compact unit that clearly describes the trigger for the behavior as well as the behavior itself.  This improves code readability and is easy to extend and modify later.

Filtering Events

With standard event handling patterns, your event handlers are triggered every time an event is raised.  This requires that you add logic to your event handlers to ignore undesirable events, which can obscure your code's intentions.

With Reactive Extensions, you can easily define qualifiers for an event stream that will filter undesirable events for you automatically.  This means that your event handler will only be triggered when a desirable event occurs, saving you from having to add filter logic.  This keeps your event handlers clean and compact and removes the risk of introducing bugs with custom filter logic.

I've leveraged Reactive Extensions this way in a helper class I use to get the phone's current location.

 Observable.FromEvent<GeoPositionStatusChangedEventArgs>(
    ev => GeoCoordinateWatcher.StatusChanged += ev,
    ev => GeoCoordinateWatcher.StatusChanged -= ev).
    Where(args => args.EventArgs.Status == GeoPositionStatus.Ready || args.EventArgs.Status == GeoPositionStatus.Disabled).
    Take(1).
    Subscribe(
    (args) =>
    {
        if (args.EventArgs.Status == GeoPositionStatus.Ready)
        {
            RaiseCurrentLocationAvailable(new CurrentLocationAvailableEventArgs(GeoCoordinateWatcher.Position.Location));
        }
        else
        {
            RaiseLocationServicesDisabled();
        }
    },
    () =>
    {
        GeoCoordinateWatcher.Stop();

        RequestInProcess = false;
    });

This code is similar in structure to the previous example with a few important additions.

You'll notice that before the Take(1) I have added a Where clause that specifies that I am only interested in the Ready and Disabled statuses.  With this line of code, I add qualifiers to the StatusChanged event so that my event handler doesn't have to care about extraneous events.  Not only is this clean, but the event handlers will be deregistered automatically as described above once either a Ready or Disabled status is reached.

Another important addition here is the inclusion of a finalizer action in the second parameter of the Subscribe method.  The first parameter of the Subscribe method contains your typical event handler code.  This code will execute once for every interesting event that is raised.  The finalizer action will execute once after all of the interesting events have been raised.  This is your chance to perform any cleanup like you would in the finally block of a try catch statement.  You'll notice above I take this opportunity to stop the GeoCoordinateWatcher and update some state.

MSDN has a great article about using Reactive Extensions in other ways to emulate and filter location data from the phone's location services.

Composing Multiple Web Service Requests

A common feature among Windows Phone applications is a search page.  In some cases, a single search query might seed multiple web service requests that should all be joined and presented to the user as a single set of search results.

A solution to this would be to track all of the intermediate results and present the entire set of results to the user once all of the intermediate results are obtained.  The ideal solution would not only compose the multiple web service requests into a single set of search results but would also ignore stale results from potentially old search queries.

This scenario is actually called out in the Reactive Extensions for .NET Overview for Windows Phone as a typical mobile scenario for Reactive Extensions.  I was able to implement this solution with the following.

 Observable.FromEvent<SearchResultsAvailableEventArgs>(
    ev => Provider.SearchResultsAvailable += ev,
    ev => Provider.SearchResultsAvailable -= ev).
    Where(args => args.EventArgs.Query.Equals(query)).
    Take(3).
    Subscribe(
    (args) =>
    {
        if (ActiveSearchQuery.Equals(query))
        {
             SearchResults[args.EventArgs.Type] = args.EventArgs.SearchResults;
        }
    },
    () =>
    {
        if (ActiveSearchQuery.Equals(query))
        {
            var List = new List<ISearchable>();

            List.AddRange(SearchResults[typeof(Type1)]);
            List.AddRange(SearchResults[typeof(Type2)]);
            List.AddRange(SearchResults[typeof(Type3)]);

            RaiseSearchResultsAvailable(List);
        }
    });

The basic structure of this code should look familiar as it is essentially identical to the previous example.  In this scenario, I am using one search query to seed three searches for three different types of results.

With the Where clause and the Take(3), I specify that I am interested in three instances of the SearchResultsAvailable event where the results are for the search query just entered.  By passing the search query through as the Query property in the SearchResultsAvailableEventArgs, I can reference it in the Where clause with args.EventArgs.Query, which allows me to compare the entered query against the query associated with the returned search results.

In both the event handler code and the finalizer action, I ignore stale results by comparing the query associated with the returned search results against the active query I have stored in the ActiveSearchQuery property.  While the results from all search queries will be processed, the search results will only be composed for results from the active search query.  This protects against overlapping search queries when the network is slow or when the user otherwise initiates overlapping search queries.

Once all of the search results for the active search query are obtained, I compose the results in the finalizer action and raise an event to notify listeners that the search results are available.

Conclusion

The Reactive Extensions for .NET are a powerful tool to have in your arsenal.  The examples above are practical applications of handling events with Reactive Extensions that I have encountered in my experience building Windows Phone applications.

While no one article can really do justice to the power of Reactive Extensions, I hope the above examples have shed some light on the elegance and expressiveness that Reactive Extensions has to offer.

For more information about what Reactive Extensions can do for you, check out Rx Wiki as well as Channel 9.