views:

669

answers:

5

I'm writing a WinForms app which has two modes: console or GUI. Three projects within the same solution, one for the console app, one for the UI forms and the third to hold the logic that the two interfaces will both connect too. The Console app runs absolutely smoothly.

A model which holds the user-selections, it has an IList<T> where T is a local object, Step, which implements INotifyPropertyChanged, so in the UI this is mounted on to a DataGridView. All is fine at runtime, the initial state of the objects is reflected on the screen.

Each of the Step objects is a task which is performed in turn; some of the properties will change, being reflected back to the IList and passed on to the DataGridView.

This action in the UI versions is done by creating a BackgroundWorker raising events back to the UI. The Step does it thing and generates a StepResult object which is an enumerated type indicating a result (e.g. Running, NotRun, OK, NotOK, Caveat) and a string to indicate a message (because the step ran but not quite as expected, i.e. with a Caveat). Normally the actions will involve a database interaction, but in debug mode I randomly generate a result.

If the message is null, there's never a problem, but if I generate a response like this:

StepResult returnvalue = new StepResult(stat, "completed with caveat")

I get an error saying that the DataGridView was being accessed from a thread other than the thread it was created on. (I'm passing this through a custom handler which should handle the invoking when required - maybe it doesn't?)

Then if I generate a unique response, e.g. using a random number r:

StepResult returnvalue = new StepResult(stat, r.ToString());

the actions succeed with no problem, the numbers are written cleanly to the DataGridView.

I'm baffled. I'm assuming it's somehow a string literal problem, but can anyone come up with a clearer explanation?

+4  A: 

You've answered your own quesion:-

I get an error saying that the DataGridView was being accessed from a thread other than the thread it was created on.

WinForms insists that all actions performed on forms and controls are done in the context of the thread the form was created in. The reason for this is complex, but has a lot to do with the underlying Win32 API. For details, see the various entries on The Old New Thing blog.

What you need to do is use the InvokeRequired and Invoke methods to ensure that the controls are always accessed from the same thread (pseudocodeish):

object Form.SomeFunction (args)
{
  if (InvokeRequired)
  {
    return Invoke (new delegate (Form.Somefunction), args);
  }
  else
  {
    return result_of_some_action;
  }
}

Skizz

Skizz
I appreciate the InvokeRequired situation, but why does it work when I pass in a unique string, but not when passing in a hard-wired string?
Unsliced
Are the two statements given called from the same place in the same context?
Skizz
Yes. Those two examples in the question are in the same place - if I use one SUCCESS! If I use the other - FAILURE! And if the Message is not touched, but just the Status updated, then there are no errors (but the Grid gets updated). I'm still confused!
Unsliced
Use the (VS) debugger to break on the InvalidOperationException (Debug->Exceptions...) to see exactly where the exception is being generated.
Skizz
It was in the Binding Source - the event was being consumed on the worker thread, not on the UI thread. Quite why it ever works is now the question. By explicitly storing in the (ISynchronizeInvoke)form with the IList I can use that as a sledge hammer.
Unsliced
+1  A: 

Since you are doing UI binding via event subscription, you might find this helpful; it is an example I wrote a while ago that shows how to subclass BindingList<T> so that the notifications are marshalled to the UI thread automatically.

If there is no sync-context (i.e. console mode), then it reverts back to the simple direct invoke, so there is no overhead. When running in UI thread, note that this essentially uses Control.Invoke, which itself just runs the delegate directly if it is on the UI thread. So there is only any switch if the data is being edited from a non-UI thread - juts what we want ;-p

Marc Gravell
Not exactly the answer - but along the right lines!
Unsliced
Perfick! Exactly what I needed to bind to collections in a DLL from Winforms and WPF clients, thanks.
Wonko
It works only if BindingList<T> was created in the UI thread, right? But are there any solution, if BindingList<> is created outside UI thread? And No, we can't pass corresponding ISynchronizeInvoke or SynchronizationContext to BindingList<T> :(
Dmitry Lobanov
A: 

I had this same problem before. Maybe this article I posted about it can help.

http://cyberkruz.vox.com/library/post/net-problem-async-and-windows-forms.html

Matthew Kruskamp
A: 

I found this article - "Updating IBindingList from different thread" - which pointed the finger of blame to the BindingList -

Because the BindingList is not setup for async operations, you must update the BindingList from the same thread that it was controlled on.

Explicitly passing the parent form as an ISynchronizeInvoke object and creating a wrapper for the BindingList<T> did the trick.

Unsliced