Mam metodę rozszerzenia SafeInvoke Control podobną do tej, którą omawia tutaj Greg D (bez czeku IsHandleCreated).
Wołam to z System.Windows.Forms.Form
nastę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ć?
winforms
multithreading
George Mauer
źródło
źródło
IsHandleCreated
czek 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 wHandleCreated
zdarzeniu.Odpowiedzi:
Możliwe, że tworzysz kontrolki w niewłaściwym wątku. Rozważ następującą dokumentację z MSDN :
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
SafeInvoke
z wątku innego niż GUI dla kontrolki, której uchwyt nie został utworzony.uiElement
nie jest zerowa, więc sprawdzamyuiElement.InvokeRequired
. Zgodnie z dokumentacją MSDN (pogrubiona)InvokeRequired
zostanie zwrócona,false
ponieważ mimo że została utworzona w innym wątku, uchwyt nie został utworzony! To odsyła nas doelse
stanu, w którym sprawdzamyIsDisposed
lub 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?
źródło
EndInvoke
poBeginInvoke
?Znalazłem
InvokeRequired
niewiarygodne, więc po prostu używamif (!this.IsHandleCreated) { this.CreateHandle(); }
źródło
Oto moja odpowiedź na podobne pytanie :
źródło
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
InvokeRequired
iHandleCreated
przed wywołaniem invoke, nie powinieneś dostać tego wyjątku.źródło
Jeśli zamierzasz użyć a
Control
z innego wątku przed pokazaniem lub zrobieniem innych rzeczy z tymControl
, rozważ wymuszenie utworzenia jego uchwytu w konstruktorze. Odbywa się to za pomocąCreateHandle
funkcji.W projekcie wielowątkowym, w którym logika „kontrolera” nie znajduje się w formularzu WinForm, ta funkcja jest niezbędna w
Control
konstruktorach, aby uniknąć tego błędu.źródło
Dodaj to przed wywołaniem metody invoke:
while (!this.IsHandleCreated) System.Threading.Thread.Sleep(100)
źródło
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.
źródło
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
n
innych wątków asynchronicznych, których używałemnew 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ą alboObject reference not set to an instance of an object
lubInvoke 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 ...
źródło
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();
źródło
this
nie różni się w zależności od wywołania, technika w stylu javascript powinna być niepotrzebna.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; }
źródło