Im Zuge meiner Wartungsarbeiten an einer älteren Anwendung, die die Cross-Thread-Update-Regeln in Winforms schwer verletzte, habe ich die folgende Erweiterungsmethode erstellt, um illegale Aufrufe schnell zu beheben, wenn ich sie entdeckt habe:
/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of
/// updater. False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread. If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (!uiElement.IsHandleCreated)
{
// Do nothing if the handle isn't created already. The user's responsible
// for ensuring that the handle they give us exists.
return;
}
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
Beispielhafte Verwendung:
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);
Ich mag, wie ich Closures zu lesen, auch nutzen kann, obwohl forceSynchronous in diesem Fall wahr sein muss:
string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);
Ich stelle nicht in Frage, dass diese Methode nützlich ist, um illegale Aufrufe in altem Code zu korrigieren, aber was ist mit neuem Code?
Ist es gutes Design, diese Methode zu verwenden, um UI in einem Stück neuer Software zu aktualisieren, wenn Sie nicht wissen können, welcher Thread versucht, die UI zu aktualisieren, oder sollte neuer Winforms-Code im Allgemeinen eine spezifische, dedizierte Methode mit der entsprechenden Invoke()
-bezogenen Installationen für alle diese UI-Aktualisierungen? (Ich werde natürlich zuerst versuchen, die anderen geeigneten Hintergrundverarbeitungstechniken zu verwenden, z. B. BackgroundWorker).
Interessanterweise funktioniert dies nicht bei ToolStripItems . Ich habe erst kürzlich entdeckt, dass sie direkt von Komponente statt von Kontrolle . Stattdessen enthält die ToolStrip
aufzurufen, verwendet werden.
Folgemaßnahmen zu den Kommentaren:
Einige Kommentare deuten darauf hin:
if (uiElement.InvokeRequired)
sein sollte:
if (uiElement.InvokeRequired && uiElement.IsHandleCreated)
Bedenken Sie Folgendes msdn-Dokumentation :
Dies bedeutet, dass InvokeRequir return false wenn Invoke nicht erforderlich ist (der Aufruf erfolgt auf demselben Thread), oder wenn das Steuerelement in einem anderen Thread erstellt wurde anderen Thread erstellt wurde, aber das Handle des Steuerelements Handle noch nicht erstellt wurde.
In dem Fall, dass die Kontrolle noch nicht erstellt worden ist, sollten Sie nicht einfach Eigenschaften oder Methoden aufrufen, oder Ereignisse für das Steuerelement aufrufen. Dies könnte dazu führen, dass das Handle des Steuerelements im Hintergrund-Thread erstellt wird, Isolierung des Steuerelements auf einem Thread Thread ohne Nachrichtenpumpe isoliert und die Anwendung instabil.
Dagegen können Sie sich schützen auch den Wert von IsHandleCreated überprüft, wenn InvokeRequired auf einem Hintergrund-Thread false zurückgibt.
Wenn das Steuerelement in einem anderen Thread erstellt wurde, aber das Handle des Steuerelements noch nicht erstellt wurde, InvokeRequired
gibt false zurück. Das bedeutet, dass wenn InvokeRequired
gibt zurück. true
, IsHandleCreated
wird immer wahr sein. Eine erneute Prüfung ist überflüssig und falsch.