Question

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?

Was it helpful?

Solution

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

OTHER TIPS

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;
  }
}

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top