2017-07-06 2 views
0

Der folgende Code ist ein vereinfachter Proof of Concept. In einer Windows Forms-Anwendung gibt es einen FormClosing-Ereignishandler, um eine Bereinigung durchzuführen. Die Anwendung startet einige Threads, die mit Control.Invoke auf das Formular schreiben. Ich weiß, dass ich Control.BeginInvoke verwenden könnte, aber ich möchte besser verstehen, was passiert und eine andere Lösung finden.Unerwarteter Blockzustand mit Control.Invoke

List<Thread> activeThreadlist; 
volatile bool goOnPolling = true; 
private void Form1_FormClosing(object sender, FormClosingEventArgs e) 
    { 
     goOnPolling = false; 
     Thread.Sleep(1000); 
     foreach (Thread element in activeThreadlist) 
      if (element.IsAlive) 
       element.Abort(); 

    } 

Ein Flag auf falsch gesetzt, um die Schleife auf den Fäden zu stoppen, sie haben Zeit zu beenden, und wenn man noch am Leben ist, wird er mit Abort beendet.

ist der von den anderen Threads ausgeführt Code:

while (goOnPolling) 
{ 
    //some long elaboration here 
    if (!goOnPolling) continue; 
    aControl.Invoke(new Action(() => 
    { 
     //display some result about the elaboration 
    })); 
    if (!goOnPolling) continue; 
    //some long elaboration here  

} 

In etwa 50% der Fälle, wenn die Form die Fäden Blöcke auf Control.Invoke geschlossen ist, so dass sie während der Ruhezeit nicht tun beenden und Abort wird aufgerufen. Ich dachte, die Flagge goOnPolling vor dem Aufrufen von Invoke wäre in 99,999% der Fälle ausreichend gewesen, aber ich lag falsch. Wie im Code erwähnt, tun die Threads lange Ausarbeitungen (mindestens 100ms), also erwartete ich goOnPolling war wahrscheinlich, während sie zu ändern. Warum liege ich falsch? Gibt es eine andere einfache Lösung, ohne zu BeginInvoke zurückzukehren, die einen zusätzlichen Thread erstellt?

aktualisieren

Nach dem Lesen der Kommentare ich den Titel klar war falsch. Ich schrieb ursprünglich Deadlock, aber es ist nur ein unerwarteter (für mich) Blockzustand. Am Anfang konnte ich nicht verstehen, warum meine Fäden noch am Leben waren, nachdem ich ihre Abschlüsse für 1000ms gewartet hatte, also setzte ich einen Abbruch, um es herauszufinden. Ich habe den Unterschied zwischen völlig falsch verstanden Invoke und BeginInvoke, danke für die Klärung.

@ Jon B

Ich stimme Pause ist besser geeignet als fortsetzen, aber dieser Code ist innerhalb while (goOnPolling) Block, so konnte ich nur einige CPU-Zyklen, nichts mehr retten.

Irgendeine Idee warum so oft goOnPolling Änderungen in den frühen Stadien von Invoke? Wenn ich eine lange Ausarbeitung mache, sollte es die meiste Zeit dort passieren.

+1

'BeginInvoke' sollte keine zusätzlichen Threads erstellen, sondern nur die Arbeit des * vorhandenen * UI-Threads in die Warteschlange stellen. Das Erstellen eines neuen Threads würde vollständig dem entgegenstehen, zu dem 'BeginInvoke' gedacht ist. –

+1

Sind Sie sich über Ihren letzten Satz sicher? Soweit ich weiß, werden sowohl "Invoke" als auch "BeginInvoke" auf dem UI-Thread ausgeführt, nur "Invoke" Blöcke vor der Rückkehr. d. h. "Invoke" = "BeginInvoke" + warte auf die Ausführung. Sie können mit 'Delegate'' Invoke'/'BeginInvoke' verwechselt werden, siehe https://stackoverflow.com/questions/229554/whats-the-difference-between-invoke-and-begininvoke. Der einzige Grund für die Verwendung von "Control.Invoke" ist, wenn Sie die aufgerufene Aktion ausführen müssen, bevor Sie mit dem aufrufenden Thread fortfahren. – Rotem

+1

Ich bin 100% positiv. "Invoke" stellt die Ausführung in der Nachrichtenschleife in eine Warteschlange und wartet dann bis zum Abschluss. 'BeginInvoke' macht genau dasselbe, blockiert aber nicht bis zum Abschluss. Das ist wahrscheinlich der Ort, von dem Ihr Deadlock kommt. Ihr UI-Thread wartet auf einen untergeordneten Thread und der untergeordnete Thread wartet darauf, dass die UI abgeschlossen wird. –

Antwort

0

Wir wissen, dass diese anderen Threads durch Invoke auf den UI-Thread zugreifen möchten, und wir wissen, dass Sie den Thread in einem Sleep binden.

Dies ist eine der wenigen Zeiten sein kann, wo Application.DoEvents die am wenigsten schlechte Option ist hier:

private void Form1_FormClosing(object sender, FormClosingEventArgs e) 
{ 
    goOnPolling = false; 
    for(int i=0;i<10;i++) 
    { 
     Application.DoEvents(); 
     Thread.Sleep(50); 
    } 
    //foreach (Thread element in activeThreadlist) 
    // if (element.IsAlive) 
    //  element.Abort(); 
} 

Und auch die Fäden mit wobei Ausnahmen geworfen zu bewältigen ändern, wenn sie zu Invoke versuchen, nachdem das Formular bereits geschlossen, anstatt es mit einem Abort niederträchtig zu versuchen.


Was warum so viele Threads in dem Zustand sind, wo sie Invoke wollen, sind, dann ist es aus dem Code unklar bisher geschrieben - aber bedenken, dass Invoke Zugriff auf den UI-Thread serialisiert. Es ist möglich, dass alle Threads aufgefordert, Invoke lange bevor der UI-Thread Verarbeitung die Windows-Nachricht, die das "Formular schließen" Ereignis übersetzt, wenn der Zugriff auf den Thread stark überlastet ist.

Verwandte Themen