Nie można wywołać Invoke lub BeginInvoke w kontrolce, dopóki nie zostanie utworzony uchwyt okna


82

Mam metodę rozszerzenia SafeInvoke Control podobną do tej, którą omawia tutaj Greg D (bez czeku IsHandleCreated).

Wołam to z System.Windows.Forms.Formnastępującego adresu:

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

Czasami (to wywołanie może pochodzić z różnych wątków) powoduje to następujący błąd:

System.InvalidOperationException wystąpił

Message= "Invoke lub BeginInvoke nie można wywołać na kontrolce, dopóki uchwyt okna nie zostanie utworzony."

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

Co się dzieje i jak to naprawić? Wiem tyle, że nie jest to problem z tworzeniem formularzy, ponieważ czasami zadziała raz i zawiedzie następnym razem, więc jaki może być problem?

PS. Naprawdę jestem okropny w WinForms, czy ktoś zna dobrą serię artykułów, które wyjaśniają cały model i jak z nim pracować?


1
Coś dziwnego dzieje się z linkiem ... znaczniki i podgląd są poprawne ... dziwne.
George Mauer

W jakich kontekstach jest wywoływany program? Czy jest kiedykolwiek wywoływana z konstruktora Forma, np.? Przydatne może być rejestrowanie komunikatów dla wywołań wyświetlanych w komunikatach wyzwalanych przez zdarzenie HandleCreated w celu sprawdzenia, czy wywołujesz funkcję show tylko dla obiektów, dla których zostały już utworzone uchwyty.
Greg D

Do czego służy aplikacja / jak jest zaprojektowana? Co robi this.Show ()? (Zakładam, że robi coś więcej niż tylko this.Visible = true;) Czy odniesienie do formularzy internetowych jest literówką?
Greg D

this.Show () jest podstawą Form.Show (), więc cokolwiek to robi. Okno dialogowe nigdy nie jest wywoływane przez konstruktora. Jest wywoływany przez implementację usługi INotifier, która ma prostą metodę Notify (string)
George Mauer

4
Patrząc na to ponownie, ponad rok później, wygląda na to, że wystąpił błąd właśnie z tego powodu, że IsHandleCreatedczek istnieje. Próbujesz zmienić właściwość (Wyślij wiadomość do) kontrolki, która nie została jeszcze utworzona. Jedną z rzeczy, które można zrobić w tej sytuacji, jest kolejkowanie delegatów przesłanych przed utworzeniem formantu, a następnie uruchamianie ich w HandleCreatedzdarzeniu.
Greg D

Odpowiedzi:


77

Możliwe, że tworzysz kontrolki w niewłaściwym wątku. Rozważ następującą dokumentację z MSDN :

Oznacza to, że InvokeRequired może zwrócić false, jeśli Invoke nie jest wymagane (wywołanie występuje w tym samym wątku) lub jeśli formant został utworzony w innym wątku, ale uchwyt kontrolki nie został jeszcze utworzony.

W przypadku, gdy uchwyt kontrolki nie został jeszcze utworzony, nie należy po prostu wywoływać właściwości, metod lub zdarzeń w kontrolce. Może to spowodować utworzenie uchwytu formantu w wątku w tle, izolowanie formantu w wątku bez pompy komunikatów i niestabilność aplikacji.

Możesz chronić się przed tym przypadkiem, sprawdzając również wartość IsHandleCreated, gdy InvokeRequired zwraca false w wątku w tle. Jeśli uchwyt sterujący nie został jeszcze utworzony, przed wywołaniem Invoke lub BeginInvoke należy poczekać, aż zostanie utworzony. Zwykle dzieje się tak tylko wtedy, gdy wątek w tle jest tworzony w konstruktorze formularza podstawowego dla aplikacji (jak w Application.Run (new MainForm ()), przed wyświetleniem formularza lub wywołaniem Application.Run.

Zobaczmy, co to oznacza dla Ciebie. (Byłoby to łatwiejsze do uzasadnienia, gdybyśmy również widzieli Twoją implementację SafeInvoke)

Zakładając, że twoja implementacja jest identyczna z tą, do której się odwołujesz, z wyjątkiem sprawdzenia przed IsHandleCreated , postępujmy zgodnie z 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();
    }
}

Rozważmy przypadek, w którym wywołujemy SafeInvokez wątku innego niż GUI dla kontrolki, której uchwyt nie został utworzony.

uiElementnie jest zerowa, więc sprawdzamy uiElement.InvokeRequired. Zgodnie z dokumentacją MSDN (pogrubiona) InvokeRequiredzostanie zwrócona, falseponieważ mimo że została utworzona w innym wątku, uchwyt nie został utworzony! To odsyła nas do elsestanu, w którym sprawdzamy IsDisposedlub natychmiast przystępujemy do wywołania przesłanej akcji ... z wątku w tle !

W tym momencie wszystkie zakłady są wyłączone z re: tej kontroli, ponieważ jej uchwyt został utworzony w wątku, który nie ma dla niej pompy komunikatów, jak wspomniano w drugim akapicie. Być może jest to przypadek, z którym się spotykasz?


Czy powinieneś dołączyć EndInvokepo BeginInvoke?
Odys

@odyodyodys: Krótka odpowiedź: Nie. To magiczny, bardzo specyficzny przypadek, w którym nie musisz. Dłuższa odpowiedź: przeczytaj komentarze do tej odpowiedzi: stackoverflow.com/a/714680/6932
Greg D,

1
Ta odpowiedź i artykuł MSDN dotyczą zwracania wartości InvokeRequired false, ponieważ Handle nie jest tworzony. Ale OP otrzymuje wyjątek, gdy Beginvoke / Invoke jest wywoływana po tym, jak InvokeRequired zwraca wartość true. W jaki sposób InvokeRequired może zwrócić wartość true, jeśli uchwyt nie został jeszcze utworzony?
thewpfguy

Jest też stan wyścigu, taki, na który wpadłem, na wrt IsDisposed. IsDisposed może mieć wartość false podczas testowania, ale stanie się true, zanim przesłana akcja zostanie w pełni wykonana. Te dwie opcje wydają się (a) ignorować InvalidOperationException i (b) używać blokady do tworzenia krytycznych sekcji z przesłanej akcji i metody usuwania kontrolki. Pierwsza jest jak włamanie, a druga to ból.
blearyeye

37

Znalazłem InvokeRequiredniewiarygodne, więc po prostu używam

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

5
Czy to nie może spowodować, że w różnych wątkach zostaną utworzone dwa uchwyty? Uchwyt powinien zostać utworzony, wystarczy poprawić timing / kolejność wydarzeń ..
Denise Skidmore

Fajnie - wolę to niż dostęp do tego. Traktuj jako (a) nie masz nieużywanej zmiennej i (b) oczywiste jest, co się dzieje
Dunc

5
MSDN: „W przypadku, gdy uchwyt formantu nie został jeszcze utworzony, nie należy po prostu wywoływać właściwości, metod lub zdarzeń w formancie. Może to spowodować utworzenie uchwytu formantu w wątku w tle, izolując formant w wątek bez pompy komunikatów i powodujący niestabilność aplikacji. " Celem ćwiczenia jest uniknięcie tworzenia rączki na niewłaściwej nitce. Jeśli to wywołanie pochodzi z wątku, który nie jest wątkiem gui, bang - jesteś martwy.
Greg D,

25

Oto moja odpowiedź na podobne pytanie :

I pomyśleć (jeszcze nie do końca pewien), że to dlatego InvokeRequired zawsze zwróci false, jeśli kontrola nie została jeszcze załadowana / pokazane. Zrobiłem obejście, które na razie wydaje się działać, polegające na prostym odwołaniu się do uchwytu powiązanej kontrolki w jej kreatorze, na przykład:

var x = this.Handle; 

(Zobacz http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html )


Przy okazji bardzo ciekawy artykuł. Dzięki.
Yann Trevin

Dzięki, to zadziałało dla mnie, ponieważ miałem ukryty formularz, który musiał być animowany do i z wątku w tle. Odniesienie do rączki było tym, co sprawiło, że zadziałało dla mnie
John Mc

Jest to nadal problem w najnowszych wersjach .net, chociaż jest to mniej błąd niż „funkcja”. Warto zauważyć, że założenie „zegarka” na obiekt i przeglądanie jego właściwości działa tak samo, jak patrzenie na uchwyt. Skończysz z jakimś bzdurnym debugowaniem kwantowym, gdzie to działa, kiedy na to spojrzysz.
Tony Cheetham

5

Metoda w poście, którą linkujesz do połączeń Invoke/BeginInvoke przed sprawdzeniem, czy uchwyt kontrolki został utworzony w przypadku, gdy jest wywoływany z wątku, który nie utworzył kontrolki.

Otrzymasz więc wyjątek, gdy Twoja metoda zostanie wywołana z wątku innego niż ten, który utworzył kontrolkę. Może się to zdarzyć w przypadku zdarzeń zdalnych lub w kolejce elementów roboczych użytkownika ...

EDYTOWAĆ

Jeśli sprawdzisz InvokeRequiredi HandleCreatedprzed wywołaniem invoke, nie powinieneś dostać tego wyjątku.


Jeśli dobrze rozumiem, mówisz, że stanie się to za każdym razem, gdy wywołujący wątek różni się od tego, w którym została utworzona kontrolka. Nie mogę zagwarantować, z jakiego wątku zostanie wywołane zdarzenie. Być może ten, który go stworzył, jest (prawdopodobnie) zupełnie innym wątkiem. Jak mam to rozwiązać?
George Mauer

tak, to prawda. Edytowałem post z warunkiem, który powinien rozwiązać problem.
Shea

Nie jestem przekonany, że tak jest. Zaktualizowałem moje pytanie na podstawie twojego komentarza, Arnshea.
Greg D

Nie rozumiem. Potrzebuję tego okna, aby się pokazać, nie jestem pewien, dlaczego IsHandleCreated jest fałszywe, ale nie wyświetlenie tego okna nie wchodzi w grę, moje pytanie dotyczy tego, dlaczego na świecie miałoby to być fałszywe
George Mauer

Uważam, że IsHandleCreated zwróci wartość false, jeśli uchwyt został zamknięty / formant został usunięty. Czy na pewno nie jesteś ukąszony przez asynchroniczne wywołanie kontrolki, która kiedyś istniała, ale już jej nie ma?
Greg D

3

Jeśli zamierzasz użyć a Controlz innego wątku przed pokazaniem lub zrobieniem innych rzeczy z tym Control, rozważ wymuszenie utworzenia jego uchwytu w konstruktorze. Odbywa się to za pomocą CreateHandlefunkcji.

W projekcie wielowątkowym, w którym logika „kontrolera” nie znajduje się w formularzu WinForm, ta funkcja jest niezbędna w Controlkonstruktorach, aby uniknąć tego błędu.


3

Dodaj to przed wywołaniem metody invoke:

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

1

Odwołaj się do uchwytu powiązanej kontrolki w jej kreatorze, na przykład:

Uwaga : uważaj na to rozwiązanie, ponieważ jeśli kontrolka ma uchwyt, wykonywanie takich czynności, jak ustawienie rozmiaru i lokalizacji jest znacznie wolniejsze. To sprawia, że InitializeComponent jest znacznie wolniejszy. Lepszym rozwiązaniem jest pozostawienie niczego w tle, zanim kontrolka nie będzie miała uchwytu.


0

Miałem ten problem z taką prostą 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; }));
    }
}

Następnie dla ninnych wątków asynchronicznych, których używałem new MyForm().UpdateLabel(text)do próby wywołania wątku interfejsu użytkownika, ale konstruktor nie daje uchwytu do wystąpienia wątku interfejsu użytkownika, więc inne wątki otrzymują inne uchwyty wystąpienia, które są albo Object reference not set to an instance of an objectlub Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Aby rozwiązać ten problem, użyłem statycznego obiektu do przytrzymania uchwytu interfejsu użytkownika:

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; });
    }
}

Myślę, że na razie działa dobrze ...


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();

to jest C # - thisnie różni się w zależności od wywołania, technika w stylu javascript powinna być niepotrzebna.
George Mauer

z pewnością próbował jasno określić, na co się przywołać. - cokolwiek
Shimon Doodkin

0

A co z tym :


    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;
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.