7

To explain this problem i put everything needed into a small sample application which hopefully explains the problem. I really tried to push everything in as less lines as possible, but in my real application these different actors don't know each other and also shouldn't. So, simple answer like "take the variable a few lines above and call Invoke on it" wouldn't work.

So let's start with the code and afterwards a little more explanation. At first there is a simple class that implements INotifyPropertyChanged:

public class MyData : INotifyPropertyChanged
{
    private string _MyText;

    public MyData()
    {
        _MyText = "Initial";
    }

    public string MyText
    {
        get { return _MyText; }

        set
        {
            _MyText = value;
            PropertyChanged(this, new PropertyChangedEventArgs("MyText"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

So nothing special about. And here the example code which can simply be put into any empty console application project:

static void Main(string[] args)
{
    // Initialize the data and bindingSource
    var myData = new MyData();
    var bindingSource = new BindingSource();
    bindingSource.DataSource = myData;

    // Initialize the form and the controls of it ...
    var form = new Form();

    // ... the TextBox including data bind to it
    var textBox = new TextBox();
    textBox.DataBindings.Add("Text", bindingSource, "MyText");
    textBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
    textBox.Dock = DockStyle.Top;
    form.Controls.Add(textBox);

    // ... the button and what happens on a click
    var button = new Button();
    button.Text = "Click me";
    button.Dock = DockStyle.Top;
    form.Controls.Add(button);

    button.Click += (_, __) =>
    {
        // Create another thread that does something with the data object
        var worker = new BackgroundWorker();

        worker.RunWorkerCompleted += (___, ____) => button.Enabled = true;
        worker.DoWork += (___, _____) =>
        {
            for (int i = 0; i < 10; i++)
            {
                // This leads to a cross-thread exception
                // but all i'm doing is simply act on a property in
                // my data and i can't see here that any gui is involved.
                myData.MyText = "Try " + i;
            }
        };

        button.Enabled = false;
        worker.RunWorkerAsync();
    };

    form.ShowDialog();
}

If you would run this code you would get a cross-thread exception by trying to change the MyText property. This comes, cause the MyData object calls PropertyChanged which will be catched by the BindindSource. This will then, according to the Binding, try to update the Text property of the TextBox. Which clearly leads to the exception.

My biggest problem here comes from the fact that the MyData object shouldn't know anything about a gui (cause it is a simple data object). Also the worker thread doesn't know anything about a gui. It simply acts on a bunch of data objects and manipulates them.

IMHO i think the BindingSource should check on which thread the receiving object is living and do an appropiate Invoke() to get the value their. Unfortunately this isn't built into it (or am i wrong?), so my question is:

How can resolve this cross-thread exception if the data object nor the worker thread know anything about a binding source that is listening for their events to push the data into a gui.

Oliver
  • 38,725
  • 7
  • 83
  • 136

7 Answers7

6

Here is the part of the above example that solves this problem:

button.Click += (_, __) =>
{
    // Create another thread that does something with the data object
    var worker = new BackgroundWorker();

    worker.DoWork += (___, _____) =>
    {
        for (int i = 0; i < 10; i++)
        {
            // This doesn't lead to any cross-thread exception
            // anymore, cause the binding source was told to
            // be quiet. When we're finished and back in the
            // gui thread tell her to fire again its events.
            myData.MyText = "Try " + i;
        }
    };

    worker.RunWorkerCompleted += (___, ____) =>
    {
        // Back in gui thread let the binding source
        // update the gui elements.
        bindingSource.ResumeBinding();
        button.Enabled = true;
    };

    // Stop the binding source from propagating
    // any events to the gui thread.
    bindingSource.SuspendBinding();
    button.Enabled = false;
    worker.RunWorkerAsync();
};

So this doesn't lead to any cross-thread exceptions anymore. The drawback of this solution is that you won't get any intermediate results shown within the textbox, but it's better than nothing.

Oliver
  • 38,725
  • 7
  • 83
  • 136
2

In Windows Froms

In cross thread i just used

// this = from on which listbox control is created.
this.Invoke(new Action(() => 
{
   //you can call all controls it will not raise exception of cross thread 
   //example 
   SomeBindingSource.ResetBindings(false); 
   Label1.Text = "any thing"
   TextBox1.Text = "any thing"
}));

and VOILA

/////////// Edit //////////

If there is chance of call from same thread it is created on then add following check

// this = from on which listbox control is created.  
     if(this.InvokeRequired)
         this.Invoke(new Action(() => { SomeBindingSource.ResetBindings(false); }));
     else
         SomeBindingSource.ResetBindings(false);
2

You can't update the BindingSource from another thread if it's bound to a winforms control. In your MyText setter you must Invoke PropertyChanged on the UI thread rather than running it directly.

If you want an extra layer of abstraction between your MyText class and the BindingSource you can do that, but you can't separate the BindngSource from the UI thread.

Brian Gordon
  • 5,993
  • 2
  • 28
  • 37
  • 1
    But the problem is that i don't know the UI thread within the `MyData` class. So how to invoke the UI thread if i don't have access to any control that's currently on the form to call `Invoke()` on it? – Oliver Aug 17 '11 at 09:32
  • @Oliver: hey you solved this issue? i am also stuck with something like this? – Mahesh KP Jan 11 '13 at 07:24
  • @mahesh: Add an own answer to this question, on how i solved this. It's not perfect, but better than nothing. – Oliver Jan 11 '13 at 08:10
  • @Oliver: i am new to this windows forms application. I have added a question about. Can you pls have a look.. http://stackoverflow.com/questions/14258488/bindingsource-cannot-be-its-own-data-source-error-when-trying-to-reset-the-b – Mahesh KP Jan 11 '13 at 08:28
1

I realize that your question was posed some time ago, but I've decided to submit an answer just in case it's helpful to someone out there.

I suggest you consider subscribing to myData's property changed event within your main application, then updating your UI. Here's what it might look like:

//This delegate will help us access the UI thread
delegate void dUpdateTextBox(string text);

//You'll need class-scope references to your variables
private MyData myData;
private TextBox textBox;

static void Main(string[] args)
{
    // Initialize the data and bindingSource
    myData = new MyData();
    myData.PropertyChanged += MyData_PropertyChanged;

    // Initialize the form and the controls of it ...
    var form = new Form();

    // ... the TextBox including data bind to it
    textBox = new TextBox();
    textBox.Dock = DockStyle.Top;
    form.Controls.Add(textBox);

    // ... the button and what happens on a click
    var button = new Button();
    button.Text = "Click me";
    button.Dock = DockStyle.Top;
    form.Controls.Add(button);

    button.Click += (_, __) =>
    {
        // Create another thread that does something with the data object
        var worker = new BackgroundWorker();

        worker.RunWorkerCompleted += (___, ____) => button.Enabled = true;
        worker.DoWork += (___, _____) =>
        {
            for (int i = 0; i < 10; i++)
            {
                myData.MyText = "Try " + i;
            }
        };

        button.Enabled = false;
        worker.RunWorkerAsync();
    };

    form.ShowDialog();
}

//This handler will be called every time "MyText" is changed
private void MyData_PropertyChanged(Object sender, PropertyChangedEventArgs e)
{
    if((MyData)sender == myData && e.PropertyName == "MyText")
    {
        //If we are certain that this method was called from "MyText",
        //then update the UI
        UpdateTextBox(((MyData)sender).MyText);
    }
}

private void UpdateTextBox(string text)
{
    //Check to see if this method call is coming in from the UI thread or not
    if(textBox.RequiresInvoke)
    {
        //If we're not on the UI thread, invoke this method from the UI thread
        textBox.BeginInvoke(new dUpdateTextBox(UpdateTextBox), text);
        return;
    }

    //If we've reached this line of code, we are on the UI thread
    textBox.Text = text;
}

Granted, this does away with the binding pattern you were trying before. However every update to MyText should be received and displayed without issue.

780Farva
  • 136
  • 9
0

I ran into a similar situation where I tried to remove a record from the binding source, the binding source is bound to a UI control that responds to changes made on the binding source.

I used the solution from Muhammad in an extension method.

    /// <summary>
    /// Executes on the UI thread, but calling thread waits for completion before continuing.        
    /// </summary>                
    public static void InvokeIfRequired<T>(this T c, Action<T> action) where T : Control
    {
        if (c.InvokeRequired)            
            c.Invoke(new Action(() => action(c)));            
        else            
            action(c);            
    }

Then it can be used like this.

this.InvokeIfRequired(frm => frm.defaultBindingSource.Remove(rec));

This way only one line of code is required wherever a control or form needs to be invoked.

I also added a BeginInvokeIfRequired extension

    /// <summary>
    /// Executes asynchronously, on a thread pool thread.
    /// </summary>
    public static void BeginInvokeIfRequired<T>(this T c, Action<T> action) where T : Control
    {
        if (c.InvokeRequired)
            c.BeginInvoke(new Action(() => { action(c); }));
        else
            action(c);
    }

as discussed here: difference between Invoke() and BeginInvoke()

CobyC
  • 1,458
  • 1
  • 11
  • 18
0

I know this is an old post, but I just ran into this issue on a winforms app and this seemed to work.

I made a subclass of BindingSource and intercepted the OnListChanged handler to invoke on the UI thread.

public class MyBindingSource : BindingSource
    {
        private readonly ISynchronizeInvoke context;

        protected override void OnListChanged(ListChangedEventArgs e)
        {
            if (context == null) base.OnListChanged(e);
            else context.InvokeIfRequired(c => base.OnListChanged(e));
        }

        public MyBindingSource(ISynchronizeInvoke context = null)
        {
            this.context = context;
        }
    }

Where InvokeIfRequired is the handy extension method mentioned by a few others in this post.

chanban
  • 335
  • 3
  • 13
0

You can try reporting progress from the background thread which will rise an event in the UI thread. Alternatively, you can try remembering the current context (your UI thread) before calling DoWork and then inside the DoWork you can use the remembered context to post data.

oleksii
  • 33,544
  • 11
  • 83
  • 149
  • Unfortunately in my real application it is not a BackgroundWorker started from the UI thread that is updating the data. In fact it is a thread that has a list of object and simply works on them. These objects are also in a second list, that is bound to a datagrid. So how does the object itself know that their are some event listeners that have to act on the ui thread? Isn't it the responsibility of the receivers to make sure that they process the event data in the right context? – Oliver Aug 17 '11 at 09:38
  • @Oliver I think you are right and it's up to the receivers who should know where to process data. Can you try doing this through the events? You can define a DataReceived event and subscribe to it from the UI (Similar to reporting progress). The objects in the list are not aware of any listeners, they just fire the event from the objects' thread, and CLR will manage the receiver's contexts automatically. If you use events, there is no need to monitor which thread fires and which receives. No? – oleksii Aug 17 '11 at 12:12
  • The object simply fires its event in its own thread, correct. Then the binding source receives it (within the objects thread) and automatically tries to update the target (also within the object thread) which leads to an cross-thread exception. The BindingSource belongs to the ui thread, but it doesn't check if it fires from the ui thread and i think this is a design flaw by Microsoft, isn't it? – Oliver Aug 18 '11 at 07:06