Even the best performing component can be hindered by incorrect use.
Below we provide practical recommendations on what should be used or
avoided in application programming. Following the tips below you will
be able to create applications with the same
efficiency as this demo application. We also provide source codes for this example to show how easy it is.
-
It is supposed that programmers already know general principles of
application design. Nevertheless, we shall provide some useful links
for better understanding of .Net environment and tools for writing
managed code: Writing Faster Managed Code: Know What Things Cost
-
It is necessary to pay attention to grid characteristics
in different working modes, to evaluate data volume to be displayed
and to find a relevant chart for better understanding of grid use.
-
Use ThreadSafeBindingList<T> instead of BindingList<T> for the following reasons:
1. BindingList<T> has poor implementation when working with objects that implement INotifyPropertyChanged interface. Here
you can find more information on BindingList<T> performance
issues. Use ThreadSafeBindingList<T> instead, as it doesn't
allow BindingList<T> to subscribe to events of objects that it
contains.
2. Another reason to use ThreadSafeBindingList<T> instead of
BindingList<T> is related to implementation of
MulticastDelegate used in events. With one subscriber, this delegate
consumes minimum memory resources, but when there are two or more
subscribers, it creates an internal subscriber collection that increases
memory consumption dramatically. The grid always subscribes to
objects that implement INotifyPropertyChanged interface and
BindingList<T> is the second subscriber.
-
If BindingList<T> is used together with objects implementing
INotifyPropertyChanged interface, it is better to avoid firing
notifications with non-existent data object properties. It is mainly
required because when a binding list receives such notification it
checks whether the firing object has the specified property. If the
firing object doesn't have such property, the BindingList<T>
generates ListChanged event specifying Reset reason and forcing the grid to rebuild all its contents.
-
Avoid using INotifyPropertyChanged interface, unless it is required.
If the object doesn't change at run-time, it is better not to use this
interface to save memory and CPU resources.
-
Add data to the end of collection (List<T>,
BindingList<T>, ThreadSafeBindingList<T> etc). Otherwise,
internal implementation of indexed collections will move and re-index
all data starting from the newly added index.
-
Avoid adding data to a binding list with more than 10 000 items when
the grid is sorted. Stop sorting to improve performance. However, if
you need to add large amounts of data in real-time to the beginning of
the grid, it's better to use the following:
grid.Sort.Enabled = false;
grid.DataSource = _datasource_;
grid.Nodes.Insert(0, _new_object_);
IBindingList bl = ...;
bl.Insert(0, _new_object_);
-
Although the grid is thread-safe with INotifyPropertyChanged and
IBindingList interfaces, use the GUI thread to work with it. This is
mainly important for adding large data volumes. Some optimization
algorithms work better when there is no need to switch between
threads. Please also note that BindingList<T> is not thread-safe.
-
Have only one message loop per application. Don't create grids in different threads.
Let us assume that the computer resources are not limitless.
Theoretically the maximum performance can be obtained if the number
of threads equals to the number of processor cores. While a lot of
threads are creating, they aren't working in parallel. In this case
cores allocate time slices to threads based on their priority and
consistently perform context switches (context switch is relatively
expansive operation). Note, maximum time slice is about 10-15 msec.
We have also take into account that each control paints its content
in the GDI/GDI+ device (via the Graphics object). While painting from
one thread all others wait for GDI device to perform painting in its
turn. Therefore if we start many message loops – it doesn't mean that
we accelerate the application. In other words the application losses
time on context switches and on drawing in GDI device.
From our point of view the application should have only one message
pump. Windows has additional mechanisms to optimize content drawing
and we are not sure that they work in case of multithreaded
environment. Of course, business logic can work in any thread, but
the graphical thread should be only one in the application.
-
Use MethodInvoker to synchronize threads.
In multi-threaded applications every call should be synchronized with the main thread containing windows message loop. Both Control.Invoke and Control.BeginInvoke
methods may accept different delegate types and their parameters.
However, this causes a serious performance issue as they call
Delegate.DynamicInvoke(params object[] args), which in turn uses
low-performance reflection. At the same time, for some delegates such as
EventHandler, MethodInvoker and WaitCallback the code is called
directly. In the time of code execution the delegate is checked for
belonging to one of the above types with specified number of parameters,
and if it doesn't – DynamicInvoke() is called.
From practical point of view the synchronization process should look as follows:
someControl.BeginInvoke(new MethodInvoker(delegate
{
}));
-
Use objects of arbitrary classes instead of collections of values of
string[] type, etc. It is more convenient and objects of arbitrary
classes consume less memory than string collections.
-
Don't format values directly in data objects, i.e. if an object
property is to return date, this field should return DateTime object
type instead of string type. Use data formatting if needed. If properties return strings, they can be compared incorrectly during data sorting. Besides, comparing two strings requires more CPU resources than comparing two DateTime objects.
class DateExample
{
...
public string FormattedDate
{
get { return string.Format("{0:yyyy-MM-dd}", _date); }
}
[Format("yyyy-MM-dd")]
public DateTime Date
{
get { return _date; }
}
}
-
Use the event-driven model, if possible. On global
scale it is more efficient than searching for rows in one or more
grids with dynamically changing data.
-
Share the same business objects in different grids. Use declarative binding. This simplifies the application code and significantly reduces memory consumption.
-
Avoid using unnecessary wrappers. Use declarative binding instead of creating intermediate classes.
-
Use statically declared EventArgs for data fields to avoid creating
numerous short-lived objects when sending notifications via
INotifyPropertyChanged interface.
class MyDataObject : INotifyPropertyChanged
{
private static readonly PropertyChangedEventArgs Value1Args = new PropertyChangedEventArgs("Value1");
private string _value1;
public string Value1
{
get { return _value1; }
set
{
if (_value1 != value)
{
_value1 = value;
FirePropertyChanged("Value1");
FirePropertyChanged(Value1Args);
}
}
}
private void FirePropertyChanged(string fieldName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(fieldName));
}
}
private void FirePropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
{
PropertyChanged(this, args);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
-
Work with business logic objects directly instead of calling Cell.Value/Row.DataAccessor["fieldId"].Value
TypeDescriptor/PropertyDescriptor are used to display values in cells when working with arbitrary data types.
The following code demonstrates value extraction from data objects.
MyDataObject dataObject = ...;
dataObject.Value1 = "some value";
PropertyDescriptor property = TypeDescriptor.GetProperties(dataObject)["Value1"];
object value = property.GetValue(dataObject);
This working principle is the same for all grids of all vendors. The
main issue is that PropertyDescriptor.GetValue() method uses
reflection. NetGrid has an optimization that enables it to receive
notifications from INotifyPropertyChanged/IBindingList without calling
data object getters. Values are required only at the time of painting
and only for visible cells. If the grid has a lot of rows, most of them
are invisible and this approach significantly saves CPU resources. It
is worth considering in application development. As an example, let's
review subscription to Grid.RowUpdated event:
grid.RowUpdated += delegate(object sender, GridRowUpdateEventArgs e)
{
MyDataObject myObject = (MyDataObject) e.Row.DataObject;
decimal lastPrice = myObject.LastPrice;
lastPrice = (decimal) e.DataField.Value;
};
-
Use new Row.IsInVisibleBounds property added to version 2.8.0.
When updating data in real time in applications, the grid stores
highlighting information for each updated cell. This default behavior
causes serious consumption of memory resources in addition to CPU
resource consumption due to processing large volume of information. As
most updates can occur outside the visible area, the programmer can
save CPU resources by using the new property.
grid.Highlighting.Enabled = false;
grid.RowUpdated += delegate(object sender, GridRowUpdateEventArgs e)
{
if(e.Row.IsInVisibleBounds)
{
MyDataObject myObject = (MyDataObject)e.Row.DataObject;
double delta = myObject.LastPrice - myObject.PrevPrice;
if(delta != 0)
{
Color color = delta > 0 ? Color.Green : Color.Red;
e.Row["LastPrice"].Highlight(TimeSpan.FromSeconds(2), Color.FromArgb(50, color));
}
}
};
We hope that these simple recommendations will help you create efficient and high-performing applications.
Dapfor .net grid has
been created by professional developers with extensive experience in
banking and telecommunication fields. Dapfor provide best .net performance grid which is adapted for real-time applications (trading application, real time trading, real time blotter) and displaying huge data volumes. Thread safety makes it safe to use with MVVM model.