|
In one of the developer forums on MSDN an interesting question was asked around using the GPS sample code that is shipped as part of the Windows Mobile 6 SDK. Running the sample code as is always worked fine for me when running the sample code inside Device Emulator and making use of FakeGPS. The developer that asked the question was however running on a real device and noticed that the application was sometimes hanging on application close down or on stopping GPS data retrieval. Since I am very passionate about location aware applications I wanted to find out what is going on here. The beauty of Windows Mobile Sample code is that all source code is available for you. Of course there are disclaimers that the code is provided “AS IS” with no warranties. I understand that and I respect that. Well, after examining the GPSSample I strongly recommend not to use this for study purposes. I probably should go one step further and also warn you against using this sample as a managed wrapper around the GPS Intermediate Driver inside your production code. I have shown location aware applications based upon the Microsoft.WindowsMobile.Samples.Location namespace in How-Do-I for Devices videos, in Webcasts and during conferences like Tech-Ed. And only now I realize that I was extremely lucky because my code samples did not crash while demonstrating them.
Let me try to explain the issues I found with this particular sample and how to create a quick and dirty fix for them. I will only concentrate on the issues I found so far, but given the nature of these issues I would not be surprised if there are more issues. First let me show you an edited version of the class diagram of the Microsoft.WindowsMobile.Sample.Location assembly. It looks pretty nice, straight forward and easy to use, as it should be to be a useful managed wrapper around a lot of native code (being the GPS Intermediate Driver implementation).

The class GpsPosition has a large number of properties that allow you to easily retrieve GPS readings like Latitude and Longitude. However, you can also see that it defines two public methods, one called GetSatellitesInView, the other called GetSatellitesInSolution. The latter is an interesting one, since it returns the number of satellites that are used to obtain our current position. This number is equal or smaller to the number of satellites in view. This number of satellites that are used to obtain our position depends on information that is passed in an array containing extended satellite information. This is where the first potential problems in the GPSSample code can be found.
Here is the original code that is available in the GpsPosition.cs source file:

As you can see, the method GetSatellitesInSolution calls the method GetSatellitesInView internally. Now imagine that the variable dwSatellitesInView in the method GetSatellitesInView is 0. That means that the return value of this method will be null. In itself this is fine, but GetSatellitesInSolution is using it and does not check for a null return value, but instead simply uses the variable called inViewSatellites. If a null was returns, this means we will get a NullReferenceException, which in the GPSSample will be reported to the user when new GPS readings are updated in the main form. Solving this problem involves two modifications, the first one being a check for null in the GetSatellitesInSolution method that can be found in the source file GpsPosition.cs:
Since GetSatellitesInSolution can now return null as well, another modification should be made in the Form1.cs file of the GPSSample that makes use of this information. The UI of Form1 is updated inside this method with the highlighted code snippet causing the next potential problem:

Besides the potential problem the above code can cause, there is also a performance improvement possible. It is not necessary to call the position.GetSatellitesInView method (which is internally used in position.GetSatellitesInSolution). Instead, the property position.SatellitesInViewCount can be used that retrieves the same number that we want to display in the sample code. If you change the highlighted code into the following code, the easy problems are solved:

A more complex issue to solve:
Using the managed wrapper around the GPS Intermediate Driver you have the possibility to subscribe to LocationChanged and DeviceStateChanged events. If you subscribe to these events, your event handler will be called each time a change occurs in either location information (received from satellites) or device information (received from the connected GPS hardware). These changes are passed to your event handler(s) on a separate thread. This has a few consequences. The first one is that you cannot simply update UI Controls with the new readings that you can retrieve inside your event handler. If you take a look in the Form1.cs source file that is part of the GpsSample, you can see the following code snippet:

There is even some meaningful comment in this code snippet, because it is not allowed to update the UI immediately on any other thread then its creator. Instead, Control.Invoke is needed to update a UI control. Control.Invoke updates the UI control using the thread that created the UI control on behalf of the thread requesting the UI update. However, Control.Invoke is a synchronous method, it will block until the UI control is actually updated. Here is where our complex problem starts to appear. In order to understand the problem, we now have to take a look at the thread inside the source file Gps.cs that sets the events to inform any subscriber of new data being available. This is the thread responsible for that action:

In this code snippet I have highlighted a block containing the statement lock(this) which is a potential cause of hanging the sample application. During its lifetime, the thread that reads location information marks the entire GPS class as critical for the entire lifetime by calling lock(this). This means that this thread has exclusive access to everything that is defined in the class Gps, including the variables of type GpsPosition and GpsDeviceState that are passed as EventArgs when the event handlers inside Form1.cs are called.
The sample UI that is defined in Form1.cs tries to retrieve location information and device state information. In listing 4 you can see that, in order to display location information, a call to a method inside a GpsPosition object is made. Since this object is passed to us from inside the worker thread of listing 5, the thread wanting to retrieve that information (through Control.Invoke, being the main thread), will be blocked until the worker thread of listing 6 terminates. Well, guess what, the worker thread can only terminate when the user selects menu entries to Exit the application or to Stop GPS. This code can never execute, because our main thread is still waiting for the worker thread of listing 6 to terminate. This is a typical deadlock situation. Two different threads are waiting on each other, without any of them being able to release locked resources. The real problem is the fact that the worker thread of listing 6 contains lock(this) to protect variables. Even the MSDN documentation indicates this as something you should not do. In the sample everything in the class is locked, and probably only a few variables need to be protected against mutual access by different threads. Also, the lock is never released until the worker thread terminates, even though good practice is to protect variables against mutual access for a short a time as possible.
Since I don’t want to change too much code right now, I take a very pragmatic approach here (and in fact, you should do something similar if you are making use of the Microsoft.WindowsMobile.Samples.Location assembly with only the modifications as I described earlier in this blog entry implemented). Leaving the GPS.cs file as is (as most of possible), you can solve this deadlock situation by modifying the UI code. You already saw the two different locations where the screen is updated through an Invoke method (in listing 5). Since Control.Invoke works synchronously, the main thread will block until the thread that refreshes satellite readings will terminate. The problem is that this latter thread will only terminate when the main thread sets an event, which it can't do because it is blocked. And there is the deadlock situation. The quick and dirty solution to solve this problem is by modifying the code inside Form1.cs, not updating UI controls through Control.Invoke, but through Control.BeginInvoke, which is an asynchronous way of updating the UI control, not blocking the main thread. So if you modify the code inside Form1.cs to this, you have solved the potential deadlock situation, even though the Gps object is entirely locked by the data retrieval thread:

This should solve at least the most dramatic issues you might run into when playing with the GpsSample application, or (even worse) when using similar code inside your own (commercial) application. There are a few more issues with the sample code, for instance omitting to unsubscribe to event handlers. In this simple example this is not dramatic, but in a real application this might lead to managed memory leaks. All in all, I think it is time to completely revise the GpsSample and the Microsoft.WindowsMobile.Samples.Location assembly. Once the conference season is over for me on December 2nd and I don’t have to deliver presentations for a whole month at least, I will probably rewrite the entire sample and the managed wrappers and of course I will publish them, most likely both on DotNETForDevices and on Code Gallery.
 What is this? | 17:51 PM |
| Blog Ref URL: http://www.dotnetfordevices.com/forum.html#385 |
|