0

I thought I understood that in order for a separate thread to make changes to the GUI in a winforms application, the method needed to be invoked. However, I've written a method to asynchronously populate a combobox, and it shows me that there's more to the story.

Here is the relevant code, with company info omitted:

private List<string> ids = new List<string>();
private BindingSource bindingSource = new BindingSource();
//...
cboIds.DataSource = bindingSource;

private void GetAvailableIds()
{
    Task idTask = new Task(
        () =>
        {
            bindingSource.Add("Searching...");    //This always updates the UI 
                                                  //without invoking

            if (cboIds.InvokeRequired)    
            {
                Invoke((MethodInvoker)delegate
                {                                //This sometimes updates the UI without 
                    cboIds.Enabled = false;      //invoking, but sometimes fails, so I                                                     
                });                              //added the check
            }
            else
                cboIds.Enabled = false;

            List<string> temp = GetUpcomingIds();

            Invoke((MethodInvoker)delegate
            {
                cboIds.Enabled = true;
                bindingSource.Clear();
                foreach (string str in temp)
                     bindingSource.Add(str);  //This never works without invoking.  
            });                               //Why, if the same operation above 
        });                                   //always works without invoking?
    idTask.Start();
}

Why is it that the initial add to the BindingSource doesn't need an invoke, setting the combobox.enabled to false sometimes needs an invoke, and the final adds to the BindingSource always need to be invoked? If they are all on the same thread, shouldn't they behave the same? Am I wrong in my assumption that they are all on the same thread?

charlieparker
  • 186
  • 1
  • 8
  • when you are accessing a winform control form a thread that had not created the control you need invoking. – Hamid Pourjam Dec 17 '14 at 22:04
  • So is the issue I'm seeing here due to the use of a Task rather than a Thread? – charlieparker Dec 17 '14 at 22:06
  • Any chance that your sample is actually not related to real code (i.e. actual code uses `async` somewhere)? – Alexei Levenkov Dec 17 '14 at 22:07
  • No, the actual code doesn't use async. – charlieparker Dec 17 '14 at 22:07
  • Also it is not clear what "And this always has to be invoked" comment supposed to mean - current sample code unconditionally calls `Invoke` so indeed it will be invoked... – Alexei Levenkov Dec 17 '14 at 22:08
  • It means that the combobox did not update unless that portion of code was invoked. I solved the problem of getting my code to work, I just want to know why I had to do what I did. – charlieparker Dec 17 '14 at 22:09
  • http://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c , when I look at Marc Gravell, answer the comments got my attention. It appears that Invoke actually may be running on UI thread while binding.add() is on the Worker thread. Could this be the reason? After all, you are making an update on the UI thread so it would make sense – Hozikimaru Dec 17 '14 at 22:30
  • I just wrote an answer for a similar SO question. Go look at my answer. It "tutorial"'s you through how to do this: http://stackoverflow.com/questions/27519769/how-to-update-a-label-each-time-in-for-loop/27530479#27530479 – Icemanind Dec 17 '14 at 22:55

1 Answers1

0

As far as when Invoke() is required, or when its asynchronous equivalent BeginInvoke() is required: you need to use it when you want to interact with any Control class object in a thread other than the thread that owns that object. Control objects are owned by the thread where they were created (this ownership is due to an underlying thread affinity for the native data structures the managed Control object represents).

If, unlike in the code example provided, you did attach your BindingSource object to some control, then you would need to use Invoke() when executing any member of that object that modifies the bound data, if you are executing that member in a thread other than the one that owns the control to which the BindingSource is bound. This is because the BindingSource object will raise events handled by the control; that interaction has to happen in the thread that owns the control, and the only way for you to accomplish that is to move the execution of the update of the BindingSource to the owning thread with the Invoke(). method.

If the BindingSource object starts out not attached to a control, and then attached later, you do not need Invoke() for any modification to the object up to, but not after, the point in time at which it's attached to a control. If it happens that your code example is not complete and you do in fact attach the bindingSource instance you've created to a control, but after the first call to the Add() method, that would explain why you require the Invoke() later, but not for the initial call to Add().

Now, with all that in mind…

Note that in the case of a DataSource binding, you do not get the same InvalidOperationException modifying the BindingSource as you would if you tried to update the control directly. Instead, the binding delays update of the control until such time as it is able to, i.e. the BindingSource is updated on the thread that owns the control.

This means that in your code example, while the control is not updated immediately when you call the first Add() method, you don't notice this because you do go back to the owning thread immediately after that, at which time the control can be updated. I.e. technically you do need to use Invoke() for that first update, but it all happens so quickly that you just don't notice to difference between doing it "the right way" and doing it "the wrong way".

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136