jump to navigation

Late binding DataGridView data with BackgroundWorker threads October 18, 2010

Posted by codinglifestyle in C#, Parallelism, Uncategorized, Winform.
Tags: , , ,
1 comment so far

In an increasingly multicore world you may notice your WinForm app never pushes your machine to 100%.  Great, right?  Erm, no.  Winforms are traditionally single threaded applications meaning we typically only tap into 1/2, 1/4, or even 1/8th of our processor’s potential.

I’ve recently been working on a utility containing a DataGridView for displaying work item data from TFS. Some of the column data had to be calculated so when displaying 500+ records the whole app was slowing down considerably. What I wanted was a delayed binding such that the cell would be initially blank, launch a thread, and update itself when the thread returned with the value. It turned out this was pretty easy.

First, use a data entity. You probably don’t have to, but I find having this layer offers an obvious place to add the changes I’ll cover below. The ideal place is in the bound property’s getter. Here you can see that the value is not yet calculated, launch a thread, and return blank or another default value in the meantime.

private int _nWeight = -1;

public int Weight

{

get

{

if (_nWeight < 0)

{

Tfs.GetWeight(Tag, GetWeightComplete);

_nWeight = 0;

}

return _nWeight;

}

}

private void GetWeightComplete(object sender, RunWorkerCompletedEventArgs e)

{

_nWeight = (int)e.Result;

if (Row < FormMain.Instance.Grid.Rows.Count)

FormMain.Instance.Grid.InvalidateRow(Row);

}

The property above represents the weight of the entity to be used in sorting or graphing by importance. Calculating the weight is a time intensive operation and is a great candidate for calculating in a worker thread. Notice a backing store, _nWeight, is used to check if the value has been calculated and also to cache the value. If _nWeight is uncached (-1) we launch the thread and return a default weight of 0 while the thread calculates the actual value. Notice when we call Tfs.GetWeight we pass the delegate, GetWeightComplete, as an argument. This function will ultimately be called when the thread returns.

public static void GetWeight(WorkItem wi, RunWorkerCompletedEventHandler onCompleteEvent)

{

BackgroundWorker worker    = new BackgroundWorker();

worker.DoWork             += new DoWorkEventHandler(GetWeightDoWork);

worker.RunWorkerCompleted += onCompleteEvent;

worker.RunWorkerAsync(wi);

}

private static void GetWeightDoWork(object sender, DoWorkEventArgs e)

{

WorkItem wi = (WorkItem)e.Argument;

int result = 0;

foreach (Revision rev in wi.Revisions)

{

if (rev.Fields[CoreField.ChangedBy].Value.ToString() == wi.Store.TeamFoundationServer.AuthenticatedUserDisplayName)

result += 1;

}

result = Math.Min(result, 10);

e.Result = result;

}

When a call is made to GetWeight you can see it uses the standard System.CompnentModel.BackgroundWorker class to manage the work. This has two main advantages: 1) an easy to use asynchronous event based pattern and 2) uses the thread pool for optimal thread management. Notice the two events, DoWork and RunWorkerCompleted are set before the thread is launched and that we can pass an arguement via RunWorkerAsync. GetWeightDoWork is called when the thread is launched and sets the result to the event arguments.  When we leave this function the RunWorkerCompleted event is called.

Finally, back in the data entity, GetWeightComplete is called when the thread has calculated the value.  The result is taken from the RunWorkercompletedEventArgs and set to the backing store. The form uses the singleton pattern and exposes the grid as a property (see here). This allows the data entity to easily invalidate the row which redraws the row taking the Weight value into account (in my case the weight determined the intensity of a yellow highlight drawn over the row indicating how often the authenticated user appeared in the work item’s revision history).

The user experience when using the above method is truely fantastic.  You get a responsive UI which immediately displays information with calculated information quickly coming in a few seconds later.  The standard binding method of the DataGridView further enhances this experience by only binding the data currently shown to the user.  So if only the first 25 rows are displayed, only those values will be calculated.  As we scroll down to show more rows the DataGridView will calculate only the newly bound rows (saving loads of time for potentially 1000’s of rows never shown).  Good luck unlocking your other cores for a better UI experience.

Advertisements

Late Binding in C# May 9, 2006

Posted by codinglifestyle in C#.
Tags: , , , ,
add a comment

A recurring nuisance for me is the ungraceful crash of a C# application due to a missing or out-of-date class library.  When linking DLLs normally the Windows Loader does the job of loading everything in to virtual memory.  As far as I know there is no way to catch an error at this point because, technically, your program never even started running.  What I would like is to be able to inform the user that a DLL is missing and exit gracefully.  This sent me googling for alternatives.  In C++ there is the concept of late binding allowing DLLs to be loaded when required.  In addition to reducing the startup time of your application an additional benefit is being able to catch the error if the DLL fails to load and handle it gracefully.  To the best of my knowledge, this is the only solution to address this problem.  Whether it makes sense to do this (vs. investing time in your installer) is up to you.

The following discusses this idea for loading unmanaged Dlls:

There is a similar concept available in .NET 1.1 which is discussed here, but I warn you this is for the more die-hard among us: http://www.codeproject.com/csharp/dyninvok.asp

Here is a bit of wisdom I found in a forum regarding late binding:

If you are using .NET 2.0 or above, this is simple. You can declare
LoadLibrary and GetProcAddress to load your dll and get the address of your
function. Once you do that, you can pass the pointer to the function to the
static GetDelegateForFunctionPointer method and have it return your delegate
(you will have to create a delegate that has the appropriate signature).

    You can get the declarations for GetProcAddress and LoadLibrary at
http://www.pinvoke.net

    If you are running .NET 1.1 or before, then you will have to have an
unmanaged shim which will load the library for you, and return the function
pointer (which you can marshal as a delegate).

You will note the codeproject article uses the unmanaged shim mentioned above.  Take a look at http://pinvoke.net mentioned above.  This looks to be very useful site.

Another idea which sprang to mind is to write a loader to walk dependencies of the destination exe.  If all dependencies are present then it would execute the application.  If not, it could inform the user or even attempt to download them.

Lastly, there is a open-source utility called Netz which will pack the exe and its dlls together in to a single exe thus avoiding the whole issue altogether.