Invoke oder BeginInvoke können für ein Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde


81

Ich habe ein SafeInvoke Steuerungserweiterung Verfahren ähnlich den Greg D bespricht hier (minus der IsHandleCreated Prüfung).

Ich nenne es System.Windows.Forms.Formwie folgt:

public void Show(string text) {
    label.SafeInvoke(()=>label.Text = text);
    this.Show();
    this.Refresh();
}

Manchmal (dieser Aufruf kann von einer Vielzahl von Threads stammen) führt dies zu folgendem Fehler:

System.InvalidOperationException aufgetreten

Message= "Invoke oder BeginInvoke können für ein Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde."

Source= "System.Windows.Forms"

StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) 
in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16

Was ist los und wie behebe ich das? Ich weiß so viel, wie es kein Problem bei der Formularerstellung ist, da es manchmal einmal funktioniert und beim nächsten Mal fehlschlägt. Was könnte das Problem sein?

PS. Ich bin wirklich sehr, sehr schrecklich bei WinForms. Kennt jemand eine gute Artikelserie, die das gesamte Modell erklärt und wie man damit arbeitet?


1
Mit dem Link ist etwas Seltsames los ... das Markup und die Vorschau sind korrekt ... seltsam.
George Mauer

In welchen Kontexten heißt Show? Wird es jemals vom Konstruktor eines Formulars aufgerufen, z. Es kann nützlich sein, Nachrichten für Aufrufe zu protokollieren, die für vom HandleCreated-Ereignis ausgelöste Nachrichten angezeigt werden sollen, um zu überprüfen, ob Sie show nur für Objekte aufrufen, deren Handles bereits erstellt wurden.
Greg D

Wofür ist die Anwendung / wie ist sie gestaltet? Was macht das.Show ()? (Ich gehe davon aus, dass es mehr als nur das tut. Sichtbar = wahr;) Ist Ihr Verweis auf Webformulare ein Tippfehler?
Greg D

this.Show () ist die Basis Form.Show (), also was auch immer das tut. Der Dialog wird niemals von einem Konstruktor aufgerufen. Es wird durch eine Implementierung eines INotifier-Dienstes aufgerufen, der eine einfache Benachrichtigungsmethode (Zeichenfolge) hat
George Mauer

4
Wenn Sie es sich ein Jahr später noch einmal ansehen, sieht es so aus, als würden Sie den Fehler genau aus dem Grund feststellen, dass die IsHandleCreatedPrüfung vorhanden ist. Sie versuchen, eine Eigenschaft eines noch nicht erstellten Steuerelements zu ändern (Nachricht senden an). In dieser Situation können Sie Delegierte in die Warteschlange stellen, die vor der Erstellung des Steuerelements übermittelt wurden, und sie dann im HandleCreatedEreignis ausführen .
Greg D

Antworten:


76

Möglicherweise erstellen Sie Ihre Steuerelemente im falschen Thread. Beachten Sie die folgende Dokumentation von MSDN :

Dies bedeutet, dass InvokeRequired false zurückgeben kann, wenn Invoke nicht erforderlich ist (der Aufruf erfolgt im selben Thread) oder wenn das Steuerelement in einem anderen Thread erstellt wurde, das Handle des Steuerelements jedoch noch nicht erstellt wurde.

Wenn das Handle des Steuerelements noch nicht erstellt wurde, sollten Sie nicht einfach Eigenschaften, Methoden oder Ereignisse für das Steuerelement aufrufen. Dies kann dazu führen, dass das Handle des Steuerelements im Hintergrundthread erstellt wird, wodurch das Steuerelement in einem Thread ohne Nachrichtenpumpe isoliert wird und die Anwendung instabil wird.

Sie können sich vor diesem Fall schützen, indem Sie auch den Wert von IsHandleCreated überprüfen, wenn InvokeRequired in einem Hintergrundthread false zurückgibt. Wenn das Steuerelement noch nicht erstellt wurde, müssen Sie warten, bis es erstellt wurde, bevor Sie Invoke oder BeginInvoke aufrufen. Dies geschieht normalerweise nur, wenn im Konstruktor des primären Formulars für die Anwendung ein Hintergrundthread erstellt wird (wie in Application.Run (neues MainForm ()), bevor das Formular angezeigt oder Application.Run aufgerufen wurde.

Mal sehen, was das für Sie bedeutet. (Dies wäre leichter zu begründen, wenn wir auch Ihre Implementierung von SafeInvoke sehen würden.)

Angenommen, Ihre Implementierung ist mit der referenzierten identisch, mit Ausnahme der Prüfung gegen IsHandleCreated , folgen wir der Logik:

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.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}

Stellen Sie sich den Fall vor, in dem wir SafeInvokevom Nicht-GUI-Thread nach einem Steuerelement rufen, dessen Handle nicht erstellt wurde.

uiElementist nicht null, also prüfen wir uiElement.InvokeRequired. Gemäß den MSDN-Dokumenten (fett gedruckt) InvokeRequiredwird zurückgegeben, falseda das Handle nicht erstellt wurde, obwohl es in einem anderen Thread erstellt wurde! Dies versetzt uns in den elseZustand, in dem wir IsDisposeddie übermittelte Aktion überprüfen oder sofort aufrufen ... aus dem Hintergrund-Thread !

Zu diesem Zeitpunkt sind alle Wetten für dieses Steuerelement deaktiviert, da sein Handle für einen Thread erstellt wurde, für den keine Nachrichtenpumpe vorhanden ist, wie im zweiten Absatz erwähnt. Vielleicht ist dies der Fall, dem Sie begegnen?


Sollten Sie eine EndInvokenach dem BeginInvokeeinfügen?
Odys

@odyodyodys: Kurze Antwort: Nein. Dies ist ein magischer, superspezifischer Fall, in dem Sie nicht müssen. Längere Antwort: Lesen Sie die Kommentare zu dieser Antwort: stackoverflow.com/a/714680/6932
Greg D

1
In dieser Antwort und im MSDN-Artikel geht es darum, dass InvokeRequired false zurückgibt, da Handle nicht erstellt wurde. OP erhält jedoch eine Ausnahme, wenn Beginvoke / Invoke aufgerufen wird, nachdem InvokeRequired true zurückgegeben hat. Wie kann InvokeRequired true zurückgeben, wenn das Handle noch nicht erstellt wurde?
Thewpfguy

Es gibt auch eine Rennbedingung, eine, die ich für IsDisposed ausgeführt habe. IsDisposed kann beim Testen falsch sein, wird jedoch wahr, bevor die übermittelte Aktion vollständig ausgeführt wird. Die beiden Auswahlmöglichkeiten scheinen zu sein: (a) InvalidOperationException ignorieren und (b) Sperre verwenden, um kritische Abschnitte aus der übermittelten Aktion und der Steuerelement-Entsorgungsmethode zu erstellen. Der erste fühlt sich wie ein Hack an und der zweite ist ein Schmerz.
Blearyeye

36

Ich fand das InvokeRequirednicht zuverlässig, also benutze ich es einfach

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}

5
Könnte dies nicht dazu führen, dass zwei Handles für verschiedene Threads erstellt werden? Das Handle sollte erstellt werden, Sie müssen nur Ihr Timing / Ihre Reihenfolge der Ereignisse verbessern.
Denise Skidmore

Schön - ich ziehe es vor, nur darauf zuzugreifen. Griff wie (a) Sie haben keine unbenutzte Variable und (b) es ist offensichtlich, was los ist
Dunc

5
MSDN: "Wenn das Handle des Steuerelements noch nicht erstellt wurde, sollten Sie nicht einfach Eigenschaften, Methoden oder Ereignisse für das Steuerelement aufrufen. Dies kann dazu führen, dass das Handle des Steuerelements im Hintergrundthread erstellt wird, wodurch das Steuerelement auf a isoliert wird Thread ohne Nachrichtenpumpe und macht die Anwendung instabil. " Der springende Punkt der Übung ist, zu vermeiden, dass der Griff am falschen Faden erstellt wird. Wenn dieser Aufruf von einem Thread aus erfolgt, der nicht der GUI-Thread ist, sind Sie tot.
Greg D

25

Hier ist meine Antwort auf eine ähnliche Frage :

Ich denke (noch nicht ganz sicher), dass dies daran liegt, dass InvokeRequired immer false zurückgibt, wenn das Steuerelement noch nicht geladen / angezeigt wurde. Ich habe eine Problemumgehung durchgeführt, die im Moment zu funktionieren scheint, nämlich einfach auf das Handle des zugehörigen Steuerelements in seinem Ersteller zu verweisen, wie folgt:

var x = this.Handle; 

(Siehe http://ikriv.com/de/prog/info/dotnet/MysteriousHang.html )


Sehr interessanter Artikel übrigens. Vielen Dank.
Yann Trevin

Danke, das hat bei mir funktioniert, weil ich eine versteckte Form hatte, die in einem Hintergrund-Thread ein- und ausgeblendet werden musste. Das Referenzieren des Griffs war das, was es für mich zum Laufen gebracht hat
John Mc

Dies ist in den neuesten Versionen von .net immer noch ein Problem, obwohl es weniger ein Fehler als eine "Funktion" ist. Es ist erwähnenswert, dass das Setzen einer "Uhr" auf das Objekt und das Durchsuchen seiner Eigenschaften dasselbe bedeutet wie das Betrachten des Handles. Sie landen mit einem Quanten-Debugging-Unsinn, bei dem es funktioniert, wenn Sie es sich ansehen.
Tony Cheetham

5

Die Methode in dem Beitrag, den Sie mit Aufrufen verknüpfen Invoke/ BeginInvokebevor Sie überprüfen, ob das Handle des Steuerelements erstellt wurde, falls es von einem Thread aufgerufen wird, der das Steuerelement nicht erstellt hat.

Sie erhalten also die Ausnahme, wenn Ihre Methode von einem anderen Thread als dem aufgerufen wird, der das Steuerelement erstellt hat. Dies kann durch Remoting von Ereignissen oder in der Warteschlange befindliche Arbeitsbenutzerelemente geschehen ...

BEARBEITEN

Wenn Sie überprüfen InvokeRequiredund HandleCreatedvor dem Aufruf von invoke, sollten Sie diese Ausnahme nicht erhalten.


Wenn ich das richtig verstehe, sagen Sie, dass dies immer dann passiert, wenn sich der aufrufende Thread von dem unterscheidet, für den das Steuerelement erstellt wurde. Ich kann nicht garantieren, von welchem ​​Thread das Ereignis aufgerufen wird. Es könnte derjenige sein, der es erstellt hat (wahrscheinlicher), ist ein ganz anderer Thread. Wie löse ich das?
George Mauer

Ja, das ist richtig. Ich habe den Beitrag mit einer Bedingung bearbeitet, die das Problem beheben sollte.
Shea

Ich bin nicht davon überzeugt, dass dies der Fall ist. Ich habe meine Frage basierend auf Ihrem Kommentar, Arnshea, aktualisiert.
Greg D

Ich verstehe nicht. Ich brauche dieses Fenster, um zu zeigen, ich bin nicht klar, warum IsHandleCreated falsch ist, aber das Fenster nicht zu zeigen, ist keine Option. Meine Frage ist, warum in aller Welt es falsch sein würde
George Mauer

Ich glaube, dass IsHandleCreated false zurückgibt, wenn der Griff geschlossen / das Steuerelement entsorgt wurde. Sind Sie sicher, dass Sie nicht von einem asynchronen Aufruf eines Steuerelements gebissen werden, das früher vorhanden war, aber nicht mehr?
Greg D

3

Wenn Sie ein Controlaus einem anderen Thread verwenden möchten, bevor Sie das anzeigen oder andere Dinge mit dem tun Control, sollten Sie die Erstellung seines Handles im Konstruktor erzwingen. Dies erfolgt über die CreateHandleFunktion.

In einem Multithread-Projekt, in dem sich die "Controller" -Logik nicht in einer WinForm befindet, ist diese Funktion Controlfür Konstruktoren hilfreich, um diesen Fehler zu vermeiden.


3

Fügen Sie dies hinzu, bevor Sie method invoke aufrufen:

while (!this.IsHandleCreated) 
   System.Threading.Thread.Sleep(100)

1

Verweisen Sie wie folgt auf das Handle des zugeordneten Steuerelements in seinem Ersteller:

Hinweis : Seien Sie vorsichtig mit dieser Lösung. Wenn ein Steuerelement über ein Handle verfügt, ist es viel langsamer, beispielsweise Größe und Position festzulegen. Dies macht InitializeComponent viel langsamer. Eine bessere Lösung besteht darin, nichts in den Hintergrund zu stellen, bevor das Steuerelement einen Griff hat.


0

Ich hatte dieses Problem mit dieser einfachen Form:

public partial class MyForm : Form
{
    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
    }

    internal void UpdateLabel(string s)
    {
        Invoke(new Action(() => { label1.Text = s; }));
    }
}

Dann habe nich für andere asynchrone Threads new MyForm().UpdateLabel(text)versucht, den UI-Thread aufzurufen, aber der Konstruktor gibt der UI-Thread-Instanz kein Handle, sodass andere Threads andere Instanz-Handles erhalten, die entweder Object reference not set to an instance of an objectoder sind Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Um dies zu lösen, habe ich ein statisches Objekt verwendet, um das UI-Handle zu halten:

public partial class MyForm : Form
{
    private static MyForm _mf;        

    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
        _mf = this;
    }

    internal void UpdateLabel(string s)
    {
        _mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; });
    }
}

Ich denke, es funktioniert soweit gut ...


0
var that = this; // this is a form
(new Thread(()=> {

    var action= new Action(() => {
       something
    }));

    if(!that.IsDisposed)
    {
        if(that.IsHandleCreated)
        {
            //if (that.InvokeRequired)
                that.BeginInvoke(action);
            //else
            // action.Invoke();
        }
        else
            that.HandleCreated+=(sender,event) => {
                action.Invoke();
            };
    }


})).Start();

Dies ist c # - thisvariiert nicht basierend auf dem Aufruf, dass eine Technik im Javascript-Stil unnötig sein sollte.
George Mauer

Ich habe sicher versucht, deutlich zu machen, worauf ich mich berufen soll. - was auch immer
Shimon Doodkin

0

Was ist damit:


    public static bool SafeInvoke( this Control control, MethodInvoker method )
    {
        if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
        {
            if( control.InvokeRequired )
            {
                control.Invoke( method );
            }
            else
            {
                method();
            }
            return true;
        }
        else return false;
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.