views:

1155

answers:

8

For some reason, this safe-looking method raises a classic exception.

Cross-thread operation not valid: Control 'statusLabel' accessed from a thread other than the thread it was created on.

This code obviously should call an anonymous method through Invoke when invoke is required. But the exception occurs every once in a while.

Has anyone had a similar problem?

    private void SetProgressBarValue(int progressPercentage)
    {
        Action setValue = () => 
        {
            var value = progressPercentage;
            if (progressPercentage < 0)
                value = 0;
            else if (progressPercentage > 100)
                value = 100;
            statusProgressBar.Value = value;
            statusLabel.Text = string.Format("{0}%", value);
        };
        if (InvokeRequired)
            Invoke(setValue);
        else
            setValue();
    }

[UPDATE2]
After implementing John Saunders's suggestion, still got the same error

at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.System.Windows.Forms.Layout.IArrangedElement.SetBounds(Rectangle bounds, BoundsSpecified specified)
at System.Windows.Forms.Layout.DefaultLayout.xLayoutDockedControl(IArrangedElement element, Rectangle newElementBounds, Boolean measureOnly, ref Size preferredSize, ref Rectangle remainingBounds)
at System.Windows.Forms.Layout.DefaultLayout.LayoutDockedControls(IArrangedElement container, Boolean measureOnly)
at System.Windows.Forms.Layout.DefaultLayout.xLayout(IArrangedElement container, Boolean measureOnly, ref Size preferredSize)
at System.Windows.Forms.Layout.DefaultLayout.LayoutCore(IArrangedElement container, LayoutEventArgs args)
at System.Windows.Forms.Layout.LayoutEngine.Layout(Object container, LayoutEventArgs layoutEventArgs)
at System.Windows.Forms.Control.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.ScrollableControl.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Form.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.ToolStripItem.InvalidateItemLayout(String affectedProperty, Boolean invalidatePainting)
at System.Windows.Forms.ToolStripItem.OnTextChanged(EventArgs e)
at System.Windows.Forms.ToolStripItem.set_Text(String value)
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClassa.<>c__DisplayClassc.<SetProgressBarValue>b__9() in ReplacementImageProcessForm.cs: line 147
at App.Image.Replace.ReplacementImageProcessForm.InvokeIfNecessary(Control control, Action setValue) in ReplacementImageProcessForm.cs: line 156
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClassa.<SetProgressBarValue>b__7() in ReplacementImageProcessForm.cs: line 145
at App.Image.Replace.ReplacementImageProcessForm.InvokeIfNecessary(Control control, Action setValue) in ReplacementImageProcessForm.cs: line 156
at App.Image.Replace.ReplacementImageProcessForm.SetProgressBarValue(Int32 progressPercentage) in ReplacementImageProcessForm.cs: line 132
at App.Image.Replace.ReplacementImageProcessForm.replacer_BeginReplace(Object sender, EventArgs e) in ReplacementImageProcessForm.cs: line 74
at App.Image.Replace.DocumentReplacer.OnBeginReplace() in IDocumentReplacer.cs: line 87
at App.Image.Replace.DocumentReplacer.Replace(Int32 documentId, String replacementDocumentPath) in IDocumentReplacer.cs: line 123

[UPDATE] Here is the stack-trace for the completeness of the question.

at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.System.Windows.Forms.Layout.IArrangedElement.SetBounds(Rectangle bounds, BoundsSpecified specified)
at System.Windows.Forms.Layout.DefaultLayout.xLayoutDockedControl(IArrangedElement element, Rectangle newElementBounds, Boolean measureOnly, ref Size preferredSize, ref Rectangle remainingBounds)
at System.Windows.Forms.Layout.DefaultLayout.LayoutDockedControls(IArrangedElement container, Boolean measureOnly)
at System.Windows.Forms.Layout.DefaultLayout.xLayout(IArrangedElement container, Boolean measureOnly, ref Size preferredSize)
at System.Windows.Forms.Layout.DefaultLayout.LayoutCore(IArrangedElement container, LayoutEventArgs args)
at System.Windows.Forms.Layout.LayoutEngine.Layout(Object container, LayoutEventArgs layoutEventArgs)
at System.Windows.Forms.Control.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.ScrollableControl.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Form.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.ToolStripItem.InvalidateItemLayout(String affectedProperty, Boolean invalidatePainting)
at System.Windows.Forms.ToolStripItem.OnTextChanged(EventArgs e)
at System.Windows.Forms.ToolStripItem.set_Text(String value)
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClass8.<SetProgressBarValue>b__7() in ReplacementImageProcessForm.cs: line 114
at App.Image.Replace.ReplacementImageProcessForm.SetProgressBarValue(Int32 progressPercentage) in ReplacementImageProcessForm.cs: line 119
at App.Image.Replace.ReplacementImageProcessForm.replacer_BeginReplace(Object sender, EventArgs e) in ReplacementImageProcessForm.cs: line 76
at App.Image.Replace.DocumentReplacer.OnBeginReplace() in IDocumentReplacer.cs: line 72
at App.Image.Replace.DocumentReplacer.Replace(Int32 documentId, String replacementDocumentPath) in IDocumentReplacer.cs: line 108
+1  A: 
Paul Williams
@Paul: I am seeing this when I am running through debugger actually. But have not encountered this issue during stand-alone mode.
Sung Meister
OK, then this could a similar issue. In this case, the stack trace does not look like the UI thread. I didn't see a Main at the bottom. Can you verify that this code is executing in a worker thread? If so, then InvokeRequired *should* have returned true, and it is supposed to invoke back to the UI thread. Also, you might try taking out the anonymous delegate and using a delegate back to this method. I seriously doubt the anonymous delegate is the issue, but it doesn't hurt to rule it out.
Paul Williams
Hmm, why isn't the <code> tag working up there? Odd.
Paul Williams
Paul: its either \<pre\> or use the button (indent)
Henk Holterman
Thank you, Henk!
Paul Williams
+1  A: 

Try to use BeginInvoke() instead of Invoke().

Boris Lipschitz
This looks worth a shot but I am wondering if this is really necessary just to get around possible bug.
Sung Meister
-1: Why would `BeginInvoke` help at all?
John Saunders
you should avoid making other threads wait for the UI thread to do something. Invoke() is a synchronous call to UI thread, while BeginInvoke() is asynchronous. Anyway, just give it a try. Let us know if it helps.
Boris Lipschitz
+2  A: 

Try overriding from Label, to create a new label class. Override the text property and place a breakpoint on it. Change the suspect label to use your new debug class instead. I've also found this technique is great for doing some basic profiling and/or debugging on your forms if you need to work out where and how things are being updated.

public class MyLabel : Label
{
    public override string Text
    {
        get
        {
            return base.Text;
        }
        set
        {
            base.Text = value;
        }
    }
}

Use your code and try and catch heisenbug, you'll be able to break on every access to the label, so if it comes from a stacktrace which you don't expect and/or isn't from your invoke code path, you have your bug?

Spence
-1: Take a closer look at the stack trace. It's clear it's doing `statusLabel.Text = string.Format("{0}%", value)`.
John Saunders
1. Answer was provided before stacktrace was specified, not how it's marked as update.2. Explain to me how providing a technique to locate the bug is wrong.
Spence
+7  A: 

This may or may not be directly relevant to your situation, but could provide a clue. One important leaky abstraction to remember about Windows Forms is that a window Handle is not created until it is actually required. The Handle property only creates the real Windows hwnd on the first get call, which doesn't occur when a Control-derived object (like a Windows Form) is instanced. (The Control-derived object, after all, is just a .NET class.) In other words, it's a property that is lazily initialized.

I've been burned by this before: The problem in my case is that I had correctly instanced a form on a UI thread, but I was not Show()ing it until the data had come back from a web service invocation that had been operating on a worker thread. The scenario was that no one had asked for the form's Handle, ever, until it was accessed as part of the InvokeRequired check that occurred when the worker thread had completed its work. So my background worker thread asked the form: do I need InvokeRequired? The InvokeRequired implementation of the form then said: well, let me look at my Handle so I can see what thread my internal hwnd was created on, and then I'll see if you're on that same thread. And so then the Handle implementation said: well I don't yet exist, so let me create an hwnd for myself right now. (You see where this is going. Remember, we're still on the background thread, innocently accessing the InvokeRequired property.)

This resulted in the Handle (and its underlying hwnd) being created on the worker thread, which I did not own, and which didn't have a message pump set up to handle Windows messages. The result: my app locked up when other calls were made to the previously hidden window, as these calls were made on the main UI thread, which reasonably assumed that all other Control-derived objects had also been created on this thread. In other cases, this could cause strange cross-thread exceptions because InvokeRequired would return false unexpectedly, since the Handle was created on a thread that is different from the thread that the form was instanced on.

But only sometimes. I had functionality whereby the user could cause the form to Show() itself via a menu, and then it would disable itself while it populated itself with data in the background (showing a throbber animation). If they did this first, then everything would be okay: the Handle was created on the UI thread (in the menu item's event handler), and so InvokeRequired behaved as expected when the worker thread finished retrieving data from the Web service. But if my background thread that periodically ran (it was an events scheduler, similar to the Event Reminder dialog in Outlook) accessed the Web service and tried to pop up the form, and the user hadn't yet Show()n it, then the worker thread's touching InvokeRequired would cause the heartburn-inducing behavior described above.

Good luck with your heisenbug!

Nicholas Piasecki
+1  A: 

To summarize, you have a private instance method SetProgressBarValue. It is an instance method of a control or form. This control or form contains other controls, statusProgressBar and statusLabel. So, you're doing the following, in effect:

if (this.InvokeRequired)
{
    Invoke(
        (Action) delegate
                 {
                     statusProgressBar.Value = 0;                 // TOUCH
                     statusLabel.Text = string.Format("{0}%", 0); // TOUCH
                 });
}
else
{
    statusProgressBar.Value = 0;                                 // TOUCH
    statusLabel.Text = string.Format("{0}%", 0);                 // TOUCH
}

This code assumes that if this.InvokeRequired == false, that this implies statusProgressBar.InvokeRequired == false and statusLabel.InvokeRequired == false. I propose that you've found a situation where that is not true.

Try changing the code to:

private void SetProgressBarValue(int progressPercentage)
{
    InvokeIfNecessary(
        this, () =>
              {
                  var value = progressPercentage;
                  if (progressPercentage < 0)
                  {
                      value = 0;
                  }
                  else if (progressPercentage > 100)
                  {
                      value = 100;
                  }

                  InvokeIfNecessary(
                      statusProgressBar.GetCurrentParent(),
                      () => statusProgressBar.Value = value);

                  InvokeIfNecessary(
                      statusLabel.GetCurrentParent(),
                      () =>
                      statusLabel.Text = string.Format("{0}%", value));
              });
}

private static void InvokeIfNecessary(Control control, Action setValue)
{
    if (control.InvokeRequired)
    {
        control.Invoke(setValue);
    }
    else
    {
        setValue();
    }
}

I suspect you may somehow have caused the window handles of these three controls to be created on different threads. I think this code will work, even if all three window handles were created on different threads.

John Saunders
@John Saunders: Thank you, John. I am trying out your approach but since I seldom get the error in question, I will let it run for a while and see how it works out. By the way, would you mind me asking under what circumstance, "I propose that you've found a situation where that is not true" holds true?
Sung Meister
I don't know of a specific set of circumstances that can cause this. However, a wise man once said: "Once you have eliminated the impossible, that which remains, no matter how unlikely, is the truth". I agree.
John Saunders
I would be very surprised if he is using more than one UI thread... I'd love to know how this works out.
Spence
@Spence: I hope I can get this worked out as well. I will keep on updating this question until I get to the bottom of this.
Sung Meister
@Sung Meister: Did you try my code?
John Saunders
@John: Yes. I have updated the question and posted stack-trace again.
Sung Meister
A: 

John's Answer was like a medicine for me. I droped the delegate and method i had and replaced it with this inline invoking, very good! thanx

I still not could not figure out this problem... So random.. Occurs very very rarely...
Sung Meister
+1  A: 

Nicholas Piasecki's answer sheds a lot of light on this problem for me. I've often had this odd bug and I appreciate the information as to why its occurring (Handle for a control probably being lazy-loaded on first call to this.InvokeRequired from a background thread)

Im creating a lot of UI dynamically (on the UI thread) and binding to presenters (MVP pattern) which often start worker threads before the UI has first show. There are of course updates to the UI and these are marshalled onto the UI thread using this.InvokeRequired/BeginInvoke, however at this point I assume a handle may be created on a worker thread.

For me the cross-thread violation is occurring in the MainForm dispose method, when the user closes the app. As a workaround I recursively iterate through child controls disposing of them and their children when the main form is closed. Then reducing the list of controls I disposed, I eventually narrowed it down to a single control that was causing the access violation. Unfortunately I wasn't able to solve the problem directly (calling CreateControl() or CreateHandle() on the control in question did nothing to resolve the problem), but I was able to work-around by leaving my recursive disposal in place on app shutdown.

Why this works and the built-in Form.Dispose() method doesn't I don't know.

Anyway I will be more careful in future when creating controls near worker threads, now I know the Handles are lazy-loaded, so thanks!

Andrew Burnett-Thompson
A: 

I had a similar problem where I was instantiating a form which launched a background thread to fetch some data and update itself before Show() had been called. On the second instance of this action(always), I would get a cross-thread exception on the Show(). After reading Nicholas' excellent answer, I put a breakpoint in the form's constructor and checked IsHandleCreated, which returned false. I then put in this code :

        if (!this.IsHandleCreated)
            this.CreateHandle();

I haven't seen the problem since. I know msdn recommends calling CreateControl instead of CreateHandle, however, CreateControl didn't cut it for me.

Does anyone know if there are any side-effects to calling CreateHandle directly?

cs31415