In many cases, when applications are monitoring a state value, the code that executes on each state value change is relatively short and requires a few milliseconds or less to complete. For example, a simple state value change handler might set a Boolean flag or update the application display. There are, however, those cases where the state value change handler is more complex, requiring a more substantial period of time to complete. Handling such a request on the main application thread causes the user interface to momentarily freeze each time the event occurs. An application would also have similar issues if a monitored state value goes through periods of rapid change. In either case, the application user interface will appear sluggish or unresponsive because the state value change notifications are monopolizing the main application thread.
To avoid these and similar issues, you might consider architecting your application so that the code responsible for monitoring these state values executes on a thread other than the main application thread. Introducing this sort of multithreaded implementation can help your application provide a much more positive user experience because the main application thread remains available to respond to user events.
The implications of using the State and Notifications Broker API in a multithreaded application differ for native and managed developers. Native developers explicitly create the window that receives State and Notifications Broker notifications. With this being the case, native developers who want their application to handle state value change notifications on a thread separate from the main application thread can simply create a new thread and then have the new thread create the window that will handle the state value change notifications. This is no different than any other situation in which you want to handle a particular window's messages on a thread separate from the main application thread.
For managed developers, the details of window creation and message handling are abstracted by the RegistryState class; therefore, the RegistryState class needs to expose a facility for an application to request that the message handling occur on a separate thread. The RegistryState class exposes such a facility through the constructor's useFormThread parameter.
The RegistryState class exposes several constructors, two of which expose a Boolean parameter, useFormThread. When you call the RegistyrState constructor with the useFormThread parameter set to false, the RegistryState class creates a new thread that then creates the window and performs the window's message handling. For simplicity, we'll call this new thread the RegistryState thread. The newly created RegistryState thread handles all the state value change notification processing for that RegistryState class instance, which leaves the application's main thread free to handle user events. Keep in mind that the SystemState class uses the RegistryState class internally; therefore, the multithreading issues that apply to the RegistryState class apply equally to the SystemState class.
The following code example demonstrates using the SystemState class to monitor for changes in network connectivity using a thread separate from the main application thread; the code example displays the text "Connected" or "Not Connected" in a StatusBar each time the application gains or loses connectivity.
SystemState _connectionsCount = null;
private void FormMain_Load(object sender, EventArgs e)
{
_connectionsCount = new SystemState(SystemProperty.ConnectionsCount, false);
_connectionsCount.Changed += new ChangeEventHandler(ConnectionsCount_Changed);
}
void ConnectionsCount_Changed(object sender, ChangeEventArgs args)
{
int numberOfConnections = (int)args.NewValue;
string displayText = numberOfConnections > 0 ? "Connected" : "Not Connected";
_statusBar.BeginInvoke((MethodInvoker) delegate { _statusBar.Text = displayText; });
}
private void Form1_Closing(object sender, CancelEventArgs e)
{
if (_connectionsCount != null)
_connectionsCount.Dispose();
}
#if PocketPC || Smartphone
delegate void MethodInvoker();
#endif
The code constructs the _connectionsCount class variable's instance to monitor for changes in the ConnectionsCount state value using a newly created thread rather than the main application thread. We'll again call this new thread the RegistryState thread. By monitoring for state value change notifications on the RegistryState thread, the corresponding Changed event handler, the ConnectionsCount_Changed method, executes on that same thread, not the main application thread. Because the ConnectionsCount_Changed method runs on the RegistryState thread instead of the main application thread, you must use caution when interacting with other parts of your application. Your application is now a multithreaded application and requires that you use good multithreaded programming practices. One important consideration is ensuring that the event handler method safely interacts with application controls.
As you may know, Windows Forms controls can only be accessed from the thread on which they are created. Interacting with the controls from any other thread requires that the application marshal the call to the correct thread using either the control's Invoke method or BeginInvoke method. As you can see in the preceding code example, rather than attempting to directly update the _statusBar.Text property, which would throw an exception due to the cross-thread access, the ConnectionsCount_Changed method calls the _statusBar.BeginInvoke method. As a result, the code that actually assigns the displayText variable to the _statusBar.Text property executes on the main application thread.
The syntax of the _statusBar.BeginInvoke method call may seem a little strange if you're not familiar with C# anonymous methods. In the case of the preceding code example, rather than explicitly define a one-line named method to set the _statusBar.Text property, I've defined an anonymous method within the BeginInvoke method call that sets the _statusBar.Text property. There are two key benefits to using an anonymous method in this case: first, the anonymous method requires less code than explicitly defining a named method; second, the anonymous method can access the ConnectionsCount_Changed method's displayText local variable, which eliminates the need to declare and pass a parameter containing the displayText variable's value.
You'll notice that the preceding code example calls the _connectionsCount.Dispose method when the form is closing. The Dispose method plays a very important role in the RegistryState class (and the SystemState class) because the Dispose method exits the RegistryState class' notification monitoring thread. If you forget to call the Dispose method on a RegistryState class instance that was created with a useFormThread value of false, your application will never fully exit because the application's process waits indefinitely for the RegistryState thread to exit. The problem is that the RegistryState thread never exits because the RegistryState thread remains running until the application calls the Dispose method.
Note: |
|---|
|
If you would like to know more about how threads affect the application exit process, see Foreground and Background Threads in the .NET Framework Developers Guide.
|
One of the most telltale signs that you may have forgotten to call the RegistryState.Dispose method is when you're debugging the application with Visual Studio 2005 and the application appears to exit on the device or emulator but Visual Studio 2005 remains in debug mode. Visual Studio 2005 remains in debug mode because the application is still running waiting for the RegistryState thread to exit. If this happens, simply terminate the application using the Visual Studio 2005 Stop Debugging feature (Debug | Stop Debugging on the Visual Studio 2005 menu), which forcibly terminates the RegistryState thread.
The final point regarding the preceding code example is the declaration of the MethodInvoker delegate at the end of the code example, the MethodInvoker delegate is a delegate defined in the full .NET Framework but not in the .NET Compact Framework. The MethodInvoker delegate is defined as a delegate that has an empty parameter list and a void return value; I find that the MethodInvoker delegate is often useful when using anonymous methods. I've placed the MethodInvoker delegate declaration within the #if preprocessor block so that my declaration is only used when targeting Windows Mobile. This same code compiled for the desktop uses the .NET Framework defined MethodInvoker delegate.
Threading and Notification Handling: Managing Long-Running Tasks
We've seen that moving the state value change notification processing off the main application thread prevents long-running change notification processing from interfering with the application user interface. The next issue that we need to look into is how long-running state value change notification handlers affect other notification handlers within the same application. By way of an example, the following code example monitors two state values: ConnectionsCount and DisplayRotation.
SystemState _connectionsCount = null;
SystemState _displayRotation = null;
private void FormMain_Load(object sender, EventArgs e)
{
_connectionsCount = new SystemState(SystemProperty.ConnectionsCount, false);
_connectionsCount.Changed += new ChangeEventHandler(ConnectionsCount_Changed);
_displayRotation = new SystemState(SystemProperty.DisplayRotation, false);
_displayRotation.Changed += new ChangeEventHandler(DisplayRotation_Changed);
}
void ConnectionsCount_Changed(object sender, ChangeEventArgs args)
{
int numberOfConnections = (int)args.NewValue;
if (numberOfConnections > 0)
UploadDataToServer();
}
void DisplayRotation_Changed(object sender, ChangeEventArgs args)
{
int displayOrientation = (int)args.NewValue;
CalculateNewDisplayPositions(displayOrientation);
this.BeginInvoke((MethodInvoker)UpdateApplicationDisplay);
}
private void FormMain_Closing(object sender, CancelEventArgs e)
{
if (_connectionsCount != null)
_connectionsCount.Dispose();
if (_displayRotation != null)
_displayRotation.Dispose();
} When the ConnectionsCount state value changes to a value greater than zero, indicating that the device has connected to a network, the application uploads data from the local device to the server. When the DisplayRotation state value changes, indicating that the user has rotated the device display, the application recalculates the size and position of the user interface controls, and then updates the application display using the newly calculated user interface controls' sizes and positions.
Note: |
|---|
|
Be aware that in a real-life application, the simple fact that an application receives a notification that the device has connected to a network is not sufficient to begin the process of transferring data to a remote server. One also needs to verify that the connection makes the server of interest reachable. For an example of how to perform such a test, download the URLReachableDemo demo from this blog post.
|
You'll notice that each of the state values is monitored by a separate SystemState class instance with each instance constructed with the bUseFormThread parameter set to false. Just as you expect, with the bUseFormThread parameter set to false, each of the state values are monitored on a thread separate from the main application thread; however, what you may not expect is that the state values are not monitored on threads separate from one another. All RegistryState class instances created with a useFormThread parameter value of false use the same thread to monitor their respective state values.
Note: |
|---|
|
Just a reminder that the SystemState class internally uses the RegistryState class; therefore, from a threading perspective there is no difference from a SystemState class instance or a RegistryState class instance. The same thread is used for monitoring state values without regard for which of the classes you use in your application.
|
The decision of the RegistryState class implementers to use one thread to monitor all of the state values is a reasonable one. Each additional thread your application creates adds overhead and consumes resources. In most cases, a particular state values changes only occasionally, therefore, using a common thread to monitor the state values is a judicious use of resources. Your responsibility as an application developer is to implement your state value change handlers such that one handler does not interfere with another. The preceding code example demonstrates how one state value change event handler might delay the execution of another state value change handler.
The issue of concern in the preceding code example is that the ConnectionsCount state value change handler, the ConnectionsCount_Changed method, can potentially run for a long period of time. Depending on the amount of data that the application must upload, the UploadDataToServer method, and therefore the ConnectionsCount_Changed method, may run for several seconds, minutes, or even possibly hours. The notification handlers for all RegistryState class instances created with a useFormThread parameter value of false share a single thread; to avoid confusion, we'll continue to refer to this thread as the RegistryState thread. This thread, the RegistryState thread, cannot perform any other work until the ConnectionsCount_Changed method returns. As a result, if the user were to rotate the device display while the ConnectionsCount_Changed method is active, the application will not update the application display until the data upload completes because the DisplayRotation_Changed method is not able to receive the DisplayRotation state value change notification until the ConnectionsCount_Changed method returns and makes the RegistryState thread available to handle other notifications.
Resolving the problem of the two state value notification handlers competing for the same thread is easily resolved in the preceding code example. In this case, the DisplayRotation state value is being monitored with the wrong thread. The processing to handle a change in the DisplayRotation state value is tied directly to the user interface. With this being the case, the main application thread is the most reasonable choice to handle the DisplayRotation state value change notification. With the DisplayRotation state value change handling moved to the main application thread, the two state value change handlers, DisplayRotation_Changed and ConnectionsCount_Changed, no longer compete for the same thread and therefore do not interfere with one another.
Moving the DisplayRotation state value notification handler to the main application thread resolves the issue of the ConnectionsCount state value notification handler interfering with the DisplayRotation state value notification handler but the ConnectionsCount state value notification handler, ConnectionsCount_Changed, still monopolizes the RegistryState thread making the handling of any non-user interface oriented notifications difficult. For example, most applications that need to be notified when a connection becomes available also need to be notified when a connection is no longer available. Consider the following ConnectionsCount_Changed method implementation.
void ConnectionsCount_Changed(object sender, ChangeEventArgs args)
{
if (numberOfConnections > 0)
UploadDataToServer();
else
PauseDataUpload();
}
This ConnectionsCount_Changed method implementation uploads data from the device to the server when a connection becomes available and is supposed to pause the upload process when a connection is no longer available. Unfortunately, the application still has an issue with the ConnectionsCount_Changed event handler monopolizing the RegistryState notification thread when uploading the data. When a connection becomes available, the ConnectionsCount_Changed event handler executes and calls the UploadDataToServer method, which may still run for a long period of time. Until the UploadDataToServer method returns, which then allows the ConnectionsCount_Changed event handler to return, no other notifications on the RegistryState notification thread can execute; therefore, when the network connection disconnects, the system is unable to execute the ConnectionsCount_Changed event handler with the updated ConnectionsCount state value of 0; therefore, the PauseDataUpload method is never called. Instead, the UploadDataToServer method continues executing until it throws an exception because there is no valid connection available.
The fundamental problem in both this case and the earlier DisplayRotation state value case is that the ConnectionsCount state value handler is allowed to monopolize the RegistryState thread indefinitely. State value handlers should be designed to execute quickly and then return; otherwise, you'll always have the potential that your application will run into event handling problems due to the event handling thread being monopolized by one long-running state value notification handler.
Avoiding long-running event handlers doesn't mean that your application is limited to performing only simple tasks when a state value changes. The key is to separate the event notification handling from the actual task processing that the event controls. In cases where your application needs to perform some potentially long-running tasks based on state values, you should think of your state value notification handler as the manager of the task rather than the worker. The worker should instead execute on a thread other than the RegistryState notification handler thread.
There is no one right way to manage threads from the state value event handlers. You can start a new thread to execute the required task using the Thread class. Alternatively, you can use the ThreadPool class to queue the task to execute on one of the Common Language Runtime (CLR) managed threads. Finally, you can use synchronization objects such as the AutoResetEvent or ManualResetEvent classes to start and stop the task-processing running on a separate thread.
Note: |
|---|
|
There are many caveats and situation-specific considerations when designing a multithreaded application. If you're new to working with threads, you should try to work with someone who has experience in multithreaded programming if you can; you'll also want to spend some time reading about the tools and techniques used in multithreaded programming. Here are a couple of resources that I think are a good for getting started: Developing Multithreaded Applications for the .NET Compact Framework and Multithreaded Programming with Visual Basic .NET. The Patterns and Practices group also provides a fairly comprehensive guide on .NET application scalability and multithreading: Improving .NET Application Performance and Scalability. It's a good resource but may be a bit much for someone who is just getting started in multithreaded programming.
|
The following code example demonstrates the technique of starting a new thread to perform the actual task.
Thread _uploadDataToServerThread;
void ConnectionsCount_Changed(object sender, ChangeEventArgs args)
{
int numberOfConnections = (int)args.NewValue;
if (numberOfConnections > 0)
{
ThreadStart threadStartMethod = new ThreadStart(UploadDataToServer);
_uploadDataToServerThread = new Thread(threadStartMethod) ;
_uploadDataToServerThread.Start();
}
else
PauseDataUpload();
} The new implementation of the ConnectionsCount state value change handler, ConnectionsCount_Changed, now starts a new thread to perform the actual data upload and returns immediately. In this implementation, the ConnectionsCount_Changed method executes and returns quickly, which makes the RegistryState thread available to process other state value change notifications almost immediately. Notice that the PauseDataUpload method is still run directly by the handler. This is because pausing the upload process generally involves little more than setting a flag or otherwise signaling that the upload process should stop; therefore, the PauseDataUpload method is not likely to monopolize the RegistryState thread.