Multithreading a databound app?

  • Thread starter Thread starter David Veeneman
  • Start date Start date
D

David Veeneman

I am looking for a resource to learn how to do multithreading in a data
bound app. So far, I have figured out that I can't update a databound object
on a worker thread, because that triggers a cross-thread call to the UI
control that it is bound to.

I have created a simple demo to explore the problem. The demo has a
dataGridView control, a bindingSource control, a backgroundWorker control
and a Go button. The grid is bound via the BindingSource to WidgetList,
which is a llist of WidgetItem objects derived from BindingList<T>.
WidgetList has one method, PopulateList(), which simply adds 100 WidgetItems
to itself, very slowly.

At run time, the WidgetList is instantiated and bound to the bindingSource
control in the FormLoad method. The backgroundWorker's DoWork event handler
calls the WidgetList's PopulateList() method.

I had hoped I could populate the list on a worker thread without triggering
a UI control call. I'm getting a cross-thread UI call exception, even when I
suspend binding on the bindingSource and the dataGridView.

The other demos I have downloaded all seem to fudge the issue by creating an
object in the worker thread, then binding it after the thread completes.
That won't work for me, since my production app needs to transform the same
collection in a series of operations.

Can a databound object be updated on a worker thread? If so, how is it done?
Has anyone seen any good articles that discuss the issue? Thanks.
 
David,

No, it can't be done. You should probably unbind the object itself,
perform your operation, and then bind the object back.

Hop ethis helps.
 
Thanks--but how do I unbind the object? I've tried setting the DataSource of
the bindingSource and the dataGridView to null, but I'm still getting the
same illegal cross-thread call exception on the DGV.

Thanks agsin for your help.
 
I found my answer. Thanks to those who responded to my original question!

As Nicholas Paladino pointed out, you have to disconnect a control from a
bound object before you update the object on a worker thread. Then update
it, and when the worker thread completes, rebind the resulting object.

In my demo project, I am using a dataGridView control bound to a
bindingSource control. The binding source control is bound to a WidgetList.
Here's how I fixed my code:

(1) I threw out the WidgetList member variable. Instead, I created it as a
local variable in the FormLoad event handler and set it as the
bindingSource.DataSource.

private void this_Load(object sender, EventArgs e)
{
WidgetList widgetList = new WidgetList();
widgetListBindingSource.DataSource = widgetList;
}

(2) In the button handler that triggers the background worker, I get the
WidgetList from the bindinmgSource, then clear bindingSource.DataSource. I
also show a label and progress bar to show the progress of the operation. I
pass the WidgetList to the worker thread as the argument to
RunWorkerAsync():

private void toolBarButtonGo_Click(object sender, EventArgs e)
{
statusStripLabel1.Visible = true;
statusStripProgressBar1.Visible = true;
WidgetList widgetList = (WidgetList)widgetListBindingSource.DataSource;
widgetListBindingSource.DataSource = null;
backgroundWorker1.RunWorkerAsync(widgetList);
}

(3) The backgroundWorker.DoWork event fires. I get the WidgetList from the
event args and pass it to my worker method. The worker method returns the
loaded WidgetList, which I put into the event args:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Call method to be run on worker thread
WidgetList argumentList = (WidgetList)e.Argument;
WidgetList resultList = this.PopulateList(argumentList);
e.Result = resultList;
}

Here is the worker method from my demo:

private WidgetList PopulateList(WidgetList widgetList)
{
for (int i = 0; i < 100; i++)
{
// Add new widget to the list
WidgetItem widget = new WidgetItem(i);
widgetList.Add(widget);

// Pause to simulate slow process
Thread.Sleep(100);

// Report progress
backgroundWorker1.ReportProgress(i);
}
return widgetList;
}

(4) When the worker thread completes, I get the WidgetList from the
backgroundWorker.RunWorkerCompleted event args and re-bind the binding
source to it. I also hide the progress bar and label:

private void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
WidgetList widgetList = (WidgetList)e.Result;
widgetListBindingSource.DataSource = widgetList;
statusStripLabel1.Visible = false;
statusStripProgressBar1.Visible = false;
}

(5) While the operation is in progress, my worker method periodically
instructs the background worker to report its progress (see worker method
above).

(6) The backgroundWorker.ProgressChanged event fires each time
backgroundWorkerReportProgress() is called. It simply updates the progress
bar:

private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
statusStripProgressBar1.Value = e.ProgressPercentage;
}

The overall result is that I have preserved the identity of my WidgetList
and updated it on a worker thread, without making any significant changes to
my data binding scheme.
 
An update--I subsequently discovered that SuspendBinding() and
ResumeBinding() do eliminate the cross-thread control call problem in my
original message. You don't need to set the DataSource to null.

The trick is to call SuspendBinding() before calling
BackgroundWorker.RunWorkerAsync(), and to call ResumeBinding() after the
asynchronous process completes-- in the RunWorkerCompleted() event handler
or later. Here is how I did the second step:

// Re-bind binding source control to updated widget list
widgetListBindingSource.DataSource = newWidgetList;
widgetListBindingSource.ResumeBinding();

The newWidgetList object was returned to me in the
RunWorkerCompletedEventArgs.Result property:

WidgetList widgetList = (WidgetList)e.Result;
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top