7 Stimmen

Thread-übergreifende Operation nicht gültig, auch wenn InvokeRequired verwendet wird

Ich habe ein Formular mit meinen benutzerdefinierten Steuerelementen darin.

Ich habe eine Methode in meinem Formular:

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...
  }
}

Und innerhalb der Methoden, die sich auf der else Zweig erhalte ich die erwähnte Ausnahme:
Cross-thread operation not valid: Control 'txtNumber' accessed from a thread other than the thread it was created on.

Mein Szenario ist eigentlich etwas komplizierter - ich habe es nur als Beispiel extrapoliert. Was eigentlich los ist, ist, dass ich WorkflowFoundation verwende - ich habe StateMachineActivity (CTP1), die in WorkflowApplication läuft (die in ihrem eigenen Thread läuft), ich habe ihr Ereignis abonniert, und von dort rufe ich SetEnabledOnControls . Außerdem verwende ich Lesezeichen, um meinen Arbeitsablauf fortzusetzen (und außerdem gibt es MEF auf der Seite, die nicht in das Szenario eingebunden ist).

All das ist irrelevant für mein offensichtliches Missverständnis der InvokeRequired - wie ist es möglich, dass, wenn die InvokeRequired falsch ist, ich Cross-Threaded-Ausnahme haben? Ich habe keine der Steuerelemente "manuell" erstellen - es ist alles da in der Initialize() platziert von Designer.

Kann jemand etwas Licht in diese Angelegenheit bringen?

Danke!

エディテージ Mit GWLlosa Vorschlag, habe ich die ThreadId mit System.Threading.Thread.CurrentThread.ManagedThreadId . Jetzt kommt der seltsame Teil... die Thread-ID in Initialize() ist 10. Zwischen der Übergabe der ersten 2 Zustände kommt er mit der Id 13 herein - InvokeRequired war wahr, und er wurde korrekt aufgerufen. ABER, nach dem zweiten Zustand, wenn er in SetEnabledOnControls Es ist wieder 13, aber diesmal ist InvokeRequired falsch! Wie kann das sein!? Später gelingt es natürlich nicht, untergeordnete Steuerelemente zu ändern (nicht überraschend). Könnte es sein, dass das Formular irgendwie den Thread geändert hat, in dem es sich befindet?

EDIT 2 Jetzt rufe ich mit an:

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

und sie hat IsHandleCreated auf true gesetzt, scheitert aber immer noch mit dem, was devSpeed gerichtet auf .

EDIT 3 FACEPALM :) Eine der Schaltflächen, die den Zustand wiederherstellten, war zunächst CancelButton für das Formular. Wenn es aus der Eigenschaft als solche entfernt wurde, hatte der Codebihind noch die DialogResult=Cancel für sie - so mein Formular war in der Tat schließen, und natürlich war es das Handle fehlt, so dass die InvokeRequired nicht richtige Informationen zurückgeben, und daher die Fehler.

Vielen Dank an alle! Ich habe heute eine neue Sache gelernt :)

2voto

GWLlosa Punkte 23282

Es könnte die Fehlersuche erleichtern, wenn Sie die Thread-ID protokollieren, wenn die Steuerelemente erstellt werden (in der Funktion Initialize()) und die Thread-ID kurz bevor Sie versuchen, sie zu berühren. Im Allgemeinen habe ich gesehen, dass dies passiert, wenn Sie irgendwie die Steuerelemente auf einem anderen Thread als dem, den Sie in erster Linie erwarten, erstellt haben.

0 Stimmen

Sehr hilfreich! Siehe mein Update für Ergebnisse...

0voto

VorTechS Punkte 413

Ich lief gerade in ein ähnliches Problem selbst, wo ich eine Funktion abreißen haben, und dann dynamisch erstellen Steuerelemente innerhalb eines 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 }
            }
         }

Und in einem von etwa 12 Fällen, in denen dieser Code verwendet wurde, wurde eine Thread-übergreifende Ausnahme ausgelöst. Alle Instanzen, in denen dieser Code verwendet wurde, waren so geschrieben, dass die Schnittstellenerstellung asynchron mit dem Schlüsselwort "await" erfolgt.

Auf der Grundlage des Vorschlags von GWLlosa habe ich eine Erweiterungsmethode für Steuerelemente geschrieben, um den OwningThread zu ermitteln, zu dem ein Steuerelement gehört:

    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;
    }

was deutlich machte, dass sich die Thread-ID nach einigen Iterationen tatsächlich änderte.

Tief in einigen dieser Codes waren Routinen verborgen, die zum Abrufen von Daten verwendet wurden, um die entsprechenden Steuerelemente zu füllen, und zwar durch die Verwendung von Task.Run(), die von MSDN ( https://msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx ) eindeutig fest:

Das Beispiel zeigt, dass die asynchrone Aufgabe auf einer Thread als der Hauptanwendungsthread

Sobald Task.Run() aus der Gleichung genommen wurde, hat sich der Thread des Steuerelements nicht mehr geändert. Seien Sie also vorsichtig, wie und wann Sie sie verwenden!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X