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.
'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. –
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
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. –