2008-09-29 5 views
7

Ich habe eine Winforms-Anwendung, das Problem hat mit Threading zu tun. Da ich 'MyCustomCode() aufrufen, die einen neuen Thread erstellt, und ruft die Methode ' SomeMethod() 'auf, die dann auf MessageBox.Show (...) zugreift.Winforms Threading-Problem, zweiter Thread kann nicht zugreifen 1. Hauptformulare steuert

Das Problem hat mit Threading zu tun, da der neu erstellte Thread versucht, auf ein Steuerelement zuzugreifen, das in einem anderen Thread erstellt wurde.

Ich erhalte die Fehlermeldung:

Cross-Thread-Betrieb nicht gültig: Control ‚Testform‘ von einem Thread zugegriffen andere als das Gewinde der es erstellt wurde.

public TestForm() 
{ 
    InitializeComponent(); 


    // custom code 
    // 
    MyCustomCode(); 


} 

public void SomeMethod() 
{ 

    // ***** This causes an error **** 

    MessageBox.Show(this, 
     ex.Message, 
     "Error", 
     MessageBoxButtons.OK, 
     MessageBoxIcon.Error 
    ); 
} 



private void InitializeAutoUpdater() 
{ 
     // Seperate thread is spun to keep polling for updates 
     ThreadStart ts = new ThreadStart(SomeMethod); 
     pollThread = new Thread(ts); 
     pollThread.Start(); 
} 

aktualisieren

Wenn Sie an diesem Beispiel http://www.codeproject.com/KB/cs/vanillaupdaterblock.aspx aussehen, wird die Methode CheckAndUpdate Aufruf MessageBox.Show (..) das ist, was mein Problem ist. Ich hätte gedacht, dass Code gut ist, um zu gehen!

Lustige Sache ist, dass dieser Code am Freitag gut lief ???

+0

könnte es sein, weil ich .net 3.5 installiert? Ist das ein 3,5 "Feature"? Ich bezweifle es, aber es ist die einzige Erklärung! –

+0

(Ich habe es kürzlich installiert ..) –

Antwort

9

Sie können keine UI-Elemente aus mehreren Threads aufrufen.

Eine Möglichkeit, dies zu lösen, besteht darin, die Invoke-Methode eines Steuerelements mit einem Delegaten für die Funktion aufzurufen, die die UI-Elemente verwendet (wie das Meldungsfeld). Etwas wie:

public delegate void InvokeDelegate(); 

public void SomeMethod() 
{ 

    button1.Invoke((InvokeDelegate)doUIStuff); 


} 


void doUIStuff() 
{ 
      MessageBox.Show(this, 
       ex.Message, 
       "Error", 
       MessageBoxButtons.OK, 
       MessageBoxIcon.Error 
      ); 
} 
+0

Sie sollten überprüfen, ob Aufruf erforderlich ist, z. button1.InvokeRequired. – RickL

+0

Und wenn das Handle des Formulars noch nicht erstellt wurde, gibt InvokeRequired immer false zurück. Aus diesem Grund könnte SynchronizationContext empfohlen werden. –

0

sollten Sie NICHT Verwendung BeginInvoke, sollten Sie Invoke verwenden, dann, wenn Sie verstehen, dass, können Sie sich in BeginInvoke zu verwenden, wenn wirklich benötigt wird.

0
'******************************************************************* 
' Get a new processor and fire it off on a new thread. 
'******************************************************************* 
fpProc = New Processor(confTable, paramFile, keyCount) 
AddHandler fpProc.LogEntry, AddressOf LogEntry_Handler 
Dim myThread As System.Threading.Thread = New System.Threading.Thread(AddressOf fpProc.ProcessEntry) 
myThread.Start() 

dann in der übergeordneten App Sie haben:

'************************************************************************* 
'  Sub: LogEntry_Handler() 
' Author: Ron Savage 
' Date: 08/29/2007 
' 
' This routine handles the LogEntry events raised by the Processor class 
' running in a thread. 
'************************************************************************* 
Private Sub LogEntry_Handler(ByVal logLevel As Integer, ByVal logMsg As String) Handles fProc.LogEntry 
writeLogMessage(logMsg); 
End Sub 

Das ist, was ich tue.

+2

WTF? Was ist das? – leppie

+0

Es verwendet die Ereignisnachrichtenwarteschlange, um die Kommunikation zwischen Prozessen zu verarbeiten (in diesem Fall Thread an Eltern) :-) Ich habe eine "unbekannte Anzahl" von Threads, die alle Aktualisierungen an dasselbe Elternfenster senden. –

+0

Ich stimme mit leppie überein. – RickL

7

verkanten Ausnahmen (InvalidOperationException), hier zu vermeiden, ist das Codemuster:

protected delegate void someGuiFunctionDelegate(int iParam); 

protected void someGuiFunction(int iParam) 
{ 
    if (this.InvokeRequired) 
    { 
     someGuiFunctionDelegate dlg = new 
      someGuiFunctionDelegate(this.someGuiFunction); 
     this.Invoke(dlg, new object[] { iParam }); 
     return; 
    } 

    //do something with the GUI control here 
} 

Ich bin damit einverstanden, dass dies ärgerlich ist, aber es ist ein Artefakt der Tatsache, dass GUI-Controls Fenster nicht thread- werden sicher. Die Ausnahme kann irgendwo mit einer Markierung ausgeschaltet werden, aber tu das nicht, da es extrem schwierig ist, Fehler zu finden.

1

Die Regel Nummer eins beim Multithreading ist, dass Sie die Benutzeroberfläche von Worker-Threads aus nicht berühren können. Es gibt viele Möglichkeiten, Multithreading zu implementieren, und es ist sehr schwierig, es "richtig" zu machen.

Hier ist ein prägnanter Artikel, die Ihnen helfen sollen - Updating the UI from a Secondary Thread

Und hier ist ein langwieriger Artikel, der in eingehenden bespricht Einfädeln - Multi-threading in .NET

3

Dinge einfach zu halten Sie in mit der BackGroundWorker Klasse aussehen können. Diese Klasse bietet einen Rahmen für die Verarbeitung von Threading- und Fortschrittsbenachrichtigungen. Ihr ui-Thread behandelt das Fortschrittsereignis und zeigt die Fehlermeldung an, die Sie zurückgeben.

0

prüfen InvokeRequired

0

ich praticularly wie eine rekursive Aufruf.

1

Ich weiß, das ist ein älterer Beitrag, aber ich habe kürzlich eine elegante Lösung für dieses Problem mit Generika und Erweiterungsmethoden gefunden. Dies ist eine Kombination aus den Werken des Autors und einigen Kommentaren.

eine generische Methode für Cross-Thread WinForms Zugang

http://www.codeproject.com/KB/cs/GenericCrossThread.aspx

public static void Manipulate<T>(this T control, Action<T> action) where T : Control 
{ 
    if (control.InvokeRequired) 
    { 
     control.Invoke(new Action<T, Action<T>>(Manipulate), 
        new object[] { control, action }); 
    } 
    else 
    { action(control); } 
} 

Dies kann auf folgende Weise aufgerufen werden, der Einfachheit halber ich ein Etikett verwendet.

someLabel.Manipulate(lbl => lbl.Text = "Something");