views:

90

answers:

2

Hello, I am having some threading issues with a large app I am working on (getting cross-thread exceptions). Is there a way to find the thread name/id that a particular control was created on?

The error occurs when I try to add a new control to my control's control collection. I can't really create a small, reproducible sample so I will describe it as best as I can.

I have a main control that sits on a form, call it _mainControl. In its constructor I instantiate an instance of another control, something like

ChildControl _childControl = new ChildControl();

Now _childControl exists but I do not add it to _mainControls collection yet.

Eventually, _mainControl receives an event notification that I should add the control. In the event handler I check if this.InvokeRequired and if it is, I Invoke the handler, something like the following:

AddControlEventHander(...)
{
    if(InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(AddControlEventHander);
        return;
    }
    Controls.Add(_childControl);
}

The exception is always thrown at Controls.Add ("Cross-thread operation not valid: Control '_item' accessed from a thread other than the thread it was created on").

Now, what I don't understand is how this is possible. I created _childControl on the same thread that _mainControl was created on. When I look at the threads window while I am debugging, the current thread name/id is the same when I call Control.Add as it was when the _childControl was added. However, the thing that confuses me the most are the following calls from _mainControl:

InvokeReuqired == false;
_childControl.InvokeRequired == false;
_childControl._item.InvokeRequired == true; //I made _item public just to try this and it returns true!

How is that possible? Is it possible for _childControl to be created on one thread while its children are created somehow on another? All of _childControl's children are created during initialization, as is normally done.

If anyone has any tips/suggestions as to what might be going on please let me know.

Thanks.

Edit:

In case anyone is interested, I found out what was happening. I was curious as to how a control can be created on one thread and it's children created on a another thread even though the InitializeComponent was all done on the same thread. So, I found out which thread the child was being created on using code similar to what Charles suggested below. Once I knew that, I at least knew which thread to focus on. Then I broke on the OnHandleCreated event of the child control and found the issue.

One thing I didn't know was that the handle of a control is created when the control is first made visible, not when it is created. So a thread that didn't own the control was trying to set it's visibility to true. So I added a check to see if InvokeRequired and thought that would do the trick. However, something I really didn't expect is that calling InvokeRequired will create the handle of the control if it is not created yet! This in effect causes the control to be created on the wrong thread and always return false for InvokeRequired. I got around this by touching the Handle property of the control so that it is created before InvokeRequired is called.

Thanks for the help guys :)

A: 

Your event handler needs to be something like this:

AddControlEventHander(...)
{
    if (InvokeRequired)
    {
       Invoke((MethodInvoker)delegate { Controls.Add(_childControl); });
    }
    else
    {
        Controls.Add(_childControl);
    }
}

Where you call your helper function to perform the action. You're trying to call the same method again:

    BeginInvoke(new MethodInvoker(AddControlEventHander);
                                  ^^^^^^^^^^^^^^^^^^^^^
ChrisF
I thought what I have would do the same thing. If InvokeRequired returned true, it would call BeginInvoke, invoking the same function and immediately returning. When the function is run again, InvokeRequired would be false and Controls.Add would be run instead.
Flack
@Flack - Your code structure is logically the same, but it's the fact that you are trying to call the event handler again that's the problem.
ChrisF
+1  A: 

To get the owner thread for a control, try this:

private Thread GetControlOwnerThread(Control ctrl)
{
    if (ctrl.InvokeRequired)
        ctrl.BeginInvoke(
            new Action<Control>(GetControlOwnerThread),
            new object[] {ctrl});
    else
        return System.Threading.Thread.CurrentThread;
}

Can child controls be on a different thread from the parent (container control)? Yes, it all depends on what thread was running when the control was constructed (new'ed up)

You always have to check InvokeRequired... Because you never know what thread might be calling into the method you are coding... Whether you need to check InvokeRequired separately for each child control, depends on how sure you are that all the controls were created on the same thread or not. If all controls are created when the form is created, in the same initialization routine, then you are probably safe to assume they were all created on the same thread

Charles Bretana
Hmm. So what kind of checks are required when you try to see if InvokeRequired is needed for a control or not? Do you always need to check InvokeRequired on the control and all of its children?
Flack
Yes, You always have to check InvokeRequired... Because you never know what thread might be calling into the method you are coding... Whether you need to check InvokeRequired separately for each child control, depends on how sure you are that all the controls were created on the same thread or not. If all controls are created when the form is created, in the same initialization routine, then you are probably safe to assume they were all created on the same thread.
Charles Bretana
That is what seems so strange to me. From what I can tell, _childControl and all of its children look like they are created on the same thread. Also, if _childControls children were somehow created on a different thread, I don't see how I can call _mainControl's Controls.Add since it fails when the _childControl handle is being created due to the cross-threading. Either I fail due to _childControl or due to its children. I don't see how this is possible. I will revisit it and do some refactoring. Maybe it will just go away :)
Flack
If a container control was created on threadA, and some other control was created on a different thread, say threadB, then you should be able to add the control to the container's COntrols collection on threadA, the thread the container was created on. You are modifying the container control, not the child control.
Charles Bretana