Invalid cross-stream operation, even if InvokeRequired is used

I have a form with my custom controls.

I have a method in my form:

private void SetEnabledOnControls(bool val) { if (InvokeRequired) { Invoke((Action<bool>)SetEnabledOnControls, val); } else { //do the work - iterate over child controls, //and they iterate over their children, etc... } } 

And inside the methods that are on the else branch, I get the specified exception:
Cross-thread operation not valid: Control 'txtNumber' accessed from a thread other than the thread it was created on.

My script is actually a bit more complicated - I just extrapolated this as an example. Actually, I use WorkflowFoundation - I have StateMachineActivity (CTP1) working in WorkflowApplication (which runs its own thread in it), I subscribed to this event, and from there I call SetEnabledOnControls . In addition, I use bookmarks to resume my workflow (and also there the MEF on the non-scripting side).

All this has nothing to do with my apparent misunderstanding of InvokeRequired - how is it possible that if InvokeRequired is false, do I have a cross threaded exception? I do not create any of the controls "manually" - all this in the Initialize () created by the designer.

Can anyone shed some light on this?

Thanks!

EDIT Using the GWLlosa suggestion, I tracked ThreadId using System.Threading.Thread.CurrentThread.ManagedThreadId . Now the strange part comes ... The thread identifier in Initialize () is 10. Between the passage of the first 2 states, it goes into Id 13 - InvokeRequired was true, and it was called correctly. BUT, after the second state, when it enters SetEnabledOnControls , it is again 13, but this time InvokeRequired is false! How so !? Later, of course, he cannot change control over the children (not surprisingly). Maybe the form somehow changed the thread in which she lives?

EDIT 2 Now I'm calling from:

  if (IsHandleCreated) { Invoke((Action<bool>)SetEnabledOnControls, val); } 

and it has IsHandleCreated to true, but still does not work with what devSpeed points to .

EDIT 3 FACEPALM :) One of the buttons that were in the resume state was first CancelButton for the Form. When it was removed from the property as such, the code still had DialogResult = Cancel for it - so my form really closed, and of course it did not have a handle, so InvokeRequired did not return the correct information and, therefore, errors.

Thanks everyone! Today I learned about a new thing :)

+4
source share
3 answers

You may have encountered this problem .

+1
source

This can make your debugging easier if you register a stream identifier when creating controls (in the Initialize () function) and a stream identifier just before you try to touch it. Typically, I saw this when you somehow created controls in a thread other than the one you would expect in the first place.

+2
source

I just ran into a similar problem when I have a function tearing down and then dynamically creating controls in FlowLayoutPanel:

  public static void RenderEditorInstance(DataContext dataContext, object selectedItem, Form targetForm, Control targetControl, List<DynamicUserInterface.EditorControl> editorControls, EventHandler ComboBox_SelectedIndexChanged, EventHandler TextBoxControl_TextChanged, EventHandler CheckBox_CheckChanged, EventHandler NumericUpDown_ValueChanged, CheckedListControl.ItemChecked OnItemChecked, EventHandler dateTimePicker_ValueChanged, DynamicUserInterface.DuplicationValidationFailed liveLookupValidationFailed, DynamicUserInterface.PopulateComboBoxCallback populateComboBoxCallback) { if (targetForm.InvokeRequired) { InstanceRenderer renderer = new InstanceRenderer(RenderEditorInstance); targetForm.Invoke(renderer, dataContext, selectedItem, targetForm, targetControl, editorControls, ComboBox_SelectedIndexChanged, TextBoxControl_TextChanged, CheckBox_CheckChanged, NumericUpDown_ValueChanged, OnItemChecked, dateTimePicker_ValueChanged, liveLookupValidationFailed, populateComboBoxCallback); } else { targetControl.Padding = new Padding(2); targetControl.Controls.Clear(); ...{other code doing stuff here } } } object selectedItem, Form targetForm, Control targetControl, List <DynamicUserInterface.EditorControl> editorControls, EventHandler ComboBox_SelectedIndexChanged, EventHandler TextBoxControl_TextChanged, EventHandler CheckBox_CheckChanged, EventHandler NumericUpDown_ValueChanged, CheckedListControl.ItemChecked OnItemChecked, EventHandler dateTimePicker_ValueChanged, DynamicUserInterface.DuplicationValidationFailed liveLookupValidationFailed,  public static void RenderEditorInstance(DataContext dataContext, object selectedItem, Form targetForm, Control targetControl, List<DynamicUserInterface.EditorControl> editorControls, EventHandler ComboBox_SelectedIndexChanged, EventHandler TextBoxControl_TextChanged, EventHandler CheckBox_CheckChanged, EventHandler NumericUpDown_ValueChanged, CheckedListControl.ItemChecked OnItemChecked, EventHandler dateTimePicker_ValueChanged, DynamicUserInterface.DuplicationValidationFailed liveLookupValidationFailed, DynamicUserInterface.PopulateComboBoxCallback populateComboBoxCallback) { if (targetForm.InvokeRequired) { InstanceRenderer renderer = new InstanceRenderer(RenderEditorInstance); targetForm.Invoke(renderer, dataContext, selectedItem, targetForm, targetControl, editorControls, ComboBox_SelectedIndexChanged, TextBoxControl_TextChanged, CheckBox_CheckChanged, NumericUpDown_ValueChanged, OnItemChecked, dateTimePicker_ValueChanged, liveLookupValidationFailed, populateComboBoxCallback); } else { targetControl.Padding = new Padding(2); targetControl.Controls.Clear(); ...{other code doing stuff here } } } 

And in one example, around 12, where this code was used, cross-thread exceptions occurred. All instances where this code was used were written in such a way that the construction of the interface is performed asynchronously using the β€œwait” keyword.

Using the GWLlosa suggestion, I wrote an extension method for controls to get the OwningThread property that the control belongs to:

  public static Thread OwnerThread(this Control ctrl) { Thread activeThread = null; if (ctrl.InvokeRequired) { activeThread = (Thread)ctrl.Invoke(new Func<Control, Thread>(OwnerThread), new object[] { ctrl }); } else { activeThread = Thread.CurrentThread; } return activeThread; } 

.., which emphasized that after several iterations, the thread ID really changed.

Inside this code, routines were used to retrieve the data that populated the corresponding controls using Task.Run (), which is from MSDN ( https://msdn.microsoft.com/en-us/library/hh195051(v=vs. 110) .aspx ) clearly states:

The example shows that an asynchronous task runs on a different thread than the main application thread.

After Task.Run () was deduced from the equation, the control thread has not changed. Therefore, you need to be careful how and when you use it!

0
source

Source: https://habr.com/ru/post/1332386/


All Articles