Microsoft & .NET.NETOccasionally Connected Systems Architecture: The Client

Occasionally Connected Systems Architecture: The Client

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

The greatest advantages Smart Clients have over their Internet Application counterparts are the ability to work offline and the best user experience. However, one situation where Smart Clients often fail to provide even a decent user experience is when synchronizing with the server when they reconnect. This problem becomes even more exacerbated in high volume environments where performance is absolutely critical.

Why Synchronization Makes the UI Sluggish

Often, Smart Clients show the user lists and aggregates of master data that are updated by many users. As updates arrive, these views need to be updated—new elements added to lists, fields updated in grids, and so forth. When the system is performing these activities, it needs to take control of the UI thread—the thread that enables the user to interact with the system.

When connected, Smart Clients receive an ongoing trickle of updates, where the time required to process these updates and show them to the user is quite small. However, when a large number of updates arrive at once, as occurs when the client reconnects after a period of offline activity, the time required to perform these updates can be quite large, making it difficult for the user to even move the mouse on the screen let alone get any work done.

Certain applications enable the user to perform some very CPU-intensive operations, such as historical trend analysis, simulations, and geographic mapping. If these operations need to be invoked when information arrives from the server, reconnecting the Smart Client may render it unusable for minutes at a time. Consider the implications for trading systems or military applications.

When Synchronizing, Relinquish the UI Thread ASAP

As updates arrive from the server, perform as much processing as possible on background threads. Although CPU-intensive operations will always take their toll, performing them in the background will hint to the operating system that they are not top priority. You may even want to adjust the priority of these background threads, yet this rarely produces any visible benefit.

After all processing on the server updates have been completed and all that is left is to update the UI, do so as quickly as possible and then relinquish the thread. Be aware, though, that while updating the UI, the user will not be able to interact with it. So, if the time required to show all the updates becomes significant (anything more than 0.1 seconds will be felt), consider showing them in chunks. Make sure you wait between chunks; otherwise, the net result will be worse than if you performed all the updates in one shot.

The Dangers of Data Races

Another issue that rears its ugly head in several runtime scenarios occurs when the user is updating data for which a server update has just arrived. This difficult-to-reproduce condition may cause the given entities to enter an invalid state. For instance:

[User thread] Add order line to order
[Background thread] Cancel an existing order line in the order
[Background thread] Set order total
[User thread] Set order total

As a result of poor timing, the order total does not reflect the total amount of the order lines. Hopefully, server-side validation will be able to catch this before the order is saved. However, the smart client may continue using the invalid data without even contacting the server, causing the user to make erroneous decisions as to the discount made available to the client on other orders.

This issue is a special kind of Race Condition that usually does not result in the application crashing. For that reason, it is all the more insidious. Everything appears to be working correctly in the system—that is, until the IRS audits your company. Because this issue is so difficult to detect, reproduce, or debug, it is critical that you prevent it by design.

Avoiding DeadLocks

The key to preventing data races is to prevent multiple threads from interacting with the same objects concurrently. One way of doing this is to have each object perform its own locking, yet this system heightens the chances of deadlocks as follows:

[User thread] Updated product price (Product locked)
[User thread] Update order lines connected to product
              (Order lines locked)
** before user thread can update related Order totals, a context
   switch occurs
[Background thread] Cancel customer discount (Customer locked)
[Background thread] Update orders (Orders locked)
** background thread attempts to lock order lines but those are
   already locked by the user thread
** user thread attempts to lock orders but those are already locked
   by the background thread

DEADLOCK.

Automatic Synchronization

A locking scheme that can lock groups of objects is required to prevent deadlocks. Fortunately, .NET has a built-in mechanism that does just that—the ContextBoundObject and the SynchronizationAttribute. Unfortunately, this mechanism comes with certain performance implications, to the tune of 100X the overhead of regular method calls. It is for this reason that the mechanism should not be used on the entities themselves. Instead, make use of it on Controller and Service Agent classes.

Controller classes represent the “C” in the well known MVC acronym—Model, View, Controller. The Service Agent classes are those that receive updates from the server and, in turn, update the Model objects.

The way to enable automatic synchronization for a class is to have it inherit from System.ContextBoundObject and to decorate it with the SynchronizationAttribute like so:

[Synchronization]
public class OrderController : ContextBoundObject
{
   // your code
}

Because View classes almost always inherit from the base “Form” class, they cannot inherit from ContextBoundObject as well. For this reason, these classes cannot safely change data on Model objects and must delegate those actions to the Controller classes.

Unfortunately, the result of this locking model is that, once again, the background thread will block the UI thread from servicing the user.

Messaging Provides the Solution

To prevent the background thread from blocking the UI thread for any significant period of time, it is important that it perform as few updates as possible. However, the amount of updates performed directly corresponds to the number of updates sent by the server; this number can be quite large when the client goes online again. How, then, can this be solved?

If the server were to package these updates in messages one at a time, as occurs in the online scenario, and these messages were to be stored until they could be forwarded when the client came online again, everything would just work. As each message would be received at the client, it would be quickly processed and any locks would be acquired and released before the user even noticed.

The same solution could be used by the server when publishing large amounts of data. Instead of rolling up all the data into a single message, each data item would be packaged in its own message. Optimizations might be made, depending on communications infrastructure, to send all these logical messages in one network round-trip, but such improvements would not change any client code.

Conclusion

By building on the classic MVC and Service Agent patterns, and through intelligent use of messaging and the automatic synchronization facilities found in .NET, deadlocks and data races can be avoided in Smart Clients. Without any additional effort, a high-performance user experience is maintained even during resource-intensive synchronization with the server. In the next article in this series, you’ll see the how GUIDs simplify the solution across multiple tiers.

References

About the Author

Udi Dahan is The Software Simplist, a Microsoft Solutions Architect MVP, recognized .Net expert, and a member of both the Microsoft Architects and Technologists Councils. Udi provides clients all over the world with training, mentoring, and high-end architecture consulting services, specializing in Service-Oriented, scalable, and secure .NET architecture design and Web services.

He is a member of the International Speakers Bureau of INETA, an associate member of the International Association of Software Architects (IASA), a frequent conference presenter, a Dr. Dobb’s sponsored expert on Web Services, SOA, & XML, and a regularly published author.

Udi can be contacted via his blog: www.UdiDahan.com.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories