Prev topic: .Net Grid tutorial (Part4: Data formatting and presentation)
Next topic: .Net Grid tutorial (Part6: Threadsafe BindingList)
Hardware developers often encounter limitation of performance improvement by increasing processor frequency and solve this problem by threading workloads and increasing the number of processor cores. Today many have 4-6 cores, and tomorrow they will have 16, 32 or even more cores. Single-threaded software that uses only one core can’t provide performance by definition. However, threading makes application development harder and may easily cause application crashes or deadlocks.
All .Net controls are single-threaded, which means that their methods can be called only in thread where they are running, i.e. the GUI thread. This thread also processes all window messages. To make sure that application will run safely, the programmer has to synchronize threads. There are two types of synchronization – synchronous and asynchronous. These types are available via Control.Invoke() and Control.BeginInvoke() methods accordingly.
Writing an application that correctly supports multi-threading is a very complicated task. Errors in some parts of code may dramatically impact the application as whole. It is also very important to understand thread safety implementation, including its implementation in such a complex component as .Net Grid.
Most .Net Grid methods that work with data are thread safe, i.e. such methods as Grid.Rows.Add(Object) / Row.VisibleIndex etc can be called from any thread. However, it is not the most important grid feature in multi-threaded applications. The most important new feature is thread-safe handling of notifications coming from IBindingList and INotifyPropertyChanged interfaces. These interfaces are very important for data binding and it enables separation of application logic from its representation. In other words, logic shouldn’t know its presentation in GUI. There are various popular patterns of application implementation such as MVC or MVVM, but neither of these patterns considers that logic may operate in non-GUI thread. All data binding methods send business logic notifications to data presentation layer in business logic thread.
To show the importance of implementing synchronization in data presentation layer, let’s take an example of a market and an application that gets quote changes, deals and other info from this market. All these events arrive in non-GUI thread. In event-driven model the business logic notifies GUI of these events. However, without synchronization with GUI thread the application won’t work correctly. When data binding is used, GUI components are bound to data sources (e.g. to IBindingList). Therefore, to send a notification via IBindingList.ListChanged or INotifyPropertyChanged, business logic should perform synchronization (i.e. call Control.Invoke()/Contrlol.BeginInvoke() ). This requires a link to control in the business logic. It is evident that the principle of keeping the data layer independent of the presentation layer is violated here:
C# | Copy |
---|---|
public class FeedCollection : BindingList<Feed> { private Control _control; //The method is called when data is updated (for ex. data comes from TCP/IP) void OnUpdateReceiced(...) { //Synchronize the call with the UI thread. The business level should keep a reference to //the UI control! _control.Invoke(new MethodInvoker(delegate { //Raise notification in GUI thread OnListChanged(new ListChangedEventArgs(...)); })); } } DataGrid someGrid = ...; FeedCollection feed = ...; someGrid.DataSource = feed; |
.Net Grid is different. When the grid receives notifications from INotifyPropertyChanged and IBindingList, it performs synchronization with its own thread, thus making it not necessary to perform synchronization at the data layer and to have unnecessary references to graphical controls.
Copy | |
---|---|
class FeedCollection : BindingList<Feed> { //The method is called when data is updated (for ex. data comes from TCP/IP) void OnUpdateReceiced(...) { //Raise notification in the current thread (usually from non-GUI) //Dapfor .Net Grid will synchronize threads itself. OnListChanged(new ListChangedEventArgs(...)); } } Dapfor.Net.Ui.Grid grid = ...; FeedCollection feed = ...; grid.DataSource = feed; |
Now let’s look at .Net Grid actions after synchronization. When notification is transferred to GUI thread, the grid redraws changed elements, moves rows to required positions if sorting is used, regroups rows if necessary and verifies filtering conditions hiding or displaying rows as required.
The data layer doesn’t see anything of those. Its only task is to send notifications to the grid. Let’s note that the same data source (IBindingList) can be simultaneously bound to multiple grids with different hierarchy, grouping, sorting and filtering. However, data is still independent of its presentation, including multi-threading.
In effect, this approach greatly simplifies application programming as it makes it possible to concentrate on application business model implementation and not on complex aspects of synchronizing data with GUI. This enables programmer to avoid application deadlocks and synchronization errors that are hard to discover.
As we have said above, ensuring thread safety is not a trivial task for application developers. Complex modern applications may contain a lot of assemblies. Some of them may contain codes with graphical controls, others may contain business logic, various math libraries, code for TCP/IP interaction, etc. However, limitations related to GUI operation only in one thread and thread synchronization method require unneeded and dangerous dependencies of business logic from graphical components (Control.Invoke/Control.BeginInvoke). This may seriously violate the principle of business logic independence from its presentation. Dapfor .Net Grid doesn’t just enable thread synchronization, but also makes it possible to completely avoid such dependencies using an event-driven model. It means that if the application is well architected, business logic assemblies will not (and should not!) depend on Dapfor assemblies and System.Windows.Forms libraries.