thread new method make how hilos form example ejemplo delegate cross calls c# controls dispose invoke

c# - method - this invoke new function delegate()



Evitar llamar a Invocar cuando el control esté dispuesto. (14)

Esto funciona para mi

if (this.IsHandleCreated){ Task.Delay(500).ContinueWith(_ =>{ this.Invoke(fm2); }); } else { this.Refresh(); }

Tengo el siguiente código en mi subproceso de trabajo ( ImageListView continuación se deriva de Control ):

if (mImageListView != null && mImageListView.IsHandleCreated && !mImageListView.IsDisposed) { if (mImageListView.InvokeRequired) mImageListView.Invoke( new RefreshDelegateInternal(mImageListView.RefreshInternal)); else mImageListView.RefreshInternal(); }

Sin embargo, a veces obtengo una ObjectDisposedException con el método Invoke anterior. Parece que el control puede eliminarse entre el momento en que compruebo IsDisposed y llamo Invoke . ¿Cómo puedo evitar eso?


Hay condiciones de carrera implícitas en tu código. El control se puede disponer entre su prueba IsDisposed y la prueba InvokeRequired. Hay otro entre InvokeRequired e Invoke (). No puedes arreglar esto sin asegurarte de que el control sobreviva a la vida del hilo. Dado que su hilo está generando datos para una vista de lista, debe dejar de ejecutarse antes de que desaparezca la vista de lista.

Para ello, establezca e.Cancel en el evento FormClosing y señale al hilo que se detenga con un ManualResetEvent. Cuando el hilo se complete, llame de nuevo a Form.Close (). El uso de BackgroundWorker facilita la implementación de la lógica de finalización de subprocesos, encuentra código de ejemplo en esta publicación .


Intenta usar

if(!myControl.Disposing) ; // invoke here

Tuve el mismo problema que tú. Desde que cambié a la verificación. Al desechar el control, la ObjectDisposedException se ha ido. No digo que esto lo solucione el 100% del tiempo, solo el 99%;) Todavía existe la posibilidad de una condición de carrera entre el cheque a Desechar y la llamada a invocar, pero en las pruebas que he hecho no he corrido en él (uso ThreadPool y un subproceso de trabajo).

Esto es lo que uso antes de cada llamada para invocar:

private bool IsControlValid(Control myControl) { if (myControl == null) return false; if (myControl.IsDisposed) return false; if (myControl.Disposing) return false; if (!myControl.IsHandleCreated) return false; if (AbortThread) return false; // the signal to the thread to stop processing return true; }


La realidad es que con Invoke y sus amigos, no puede protegerse completamente contra la invocación en un componente desechado, o luego obtener la excepción InvalidOperationException debido a que falta el identificador. Todavía no he visto una respuesta realmente, como la que se encuentra más abajo, en ninguno de los hilos que abordan el problema fundamental real, que no se puede resolver completamente mediante pruebas preventivas o mediante el uso de semántica de bloqueo.

Aquí está el lenguaje normal ''correcto'':

// the event handler. in this case preped for cross thread calls void OnEventMyUpdate(object sender, MyUpdateEventArgs e) { if (!this.IsHandleCreated) return; // ignore events if we arn''t ready, and for // invoke if cant listen to msg queue anyway if (InvokeRequired) Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData); else this.MyUpdate(e.MyData); } // the update function void MyUpdate(Object myData) { ... }

El problema fundamental:

Al usar la función Invocar, se utiliza la cola de mensajes de Windows, que coloca un mensaje en la cola para esperar o disparar y olvidar la llamada de hilos múltiples exactamente como Enviar o Enviar mensajes. Si hay un mensaje delante del mensaje Invocar que invalidará el componente y su identificador de ventana, o que se colocó justo después de cualquier comprobación que intente realizar, entonces tendrá un mal momento.

x thread -> PostMessage(WM_CLOSE); // put ''WM_CLOSE'' in queue y thread -> this.IsHandleCreated // yes we have a valid handle y thread -> this.Invoke(); // put ''Invoke'' in queue ui thread -> this.Destroy(); // Close processed, handle gone y thread -> throw Invalid....() // ''Send'' comes back, thrown on calling thread y

No hay una forma real de saber que el control está a punto de eliminarse de la cola, y nada realmente razonable puede hacer para "deshacer" la invocación. No importa cuántos controles realice o los bloqueos adicionales que realice, no puede impedir que alguien más emita algo como un cierre o lo desactive. Hay toneladas de senarios donde esto puede pasar.

Una solución:

Lo primero que hay que darse cuenta es que la invocación va a fallar, no es diferente de cómo un cheque (IsHandleCreated) habría ignorado el evento. Si el objetivo es proteger a la persona que llama en el subproceso que no pertenece a la UI, deberá manejar la excepción y tratarla como cualquier otra llamada que no tuvo éxito (para evitar que la aplicación se bloquee o haga lo que sea. Y a menos que vaya a reescribir / reenrolle la instalación de Invoke, la captura es su única forma de saber.

// the event handler. in this case preped for cross thread calls void OnEventMyWhatever(object sender, MyUpdateEventArgs e) { if (!this.IsHandleCreated) return; if (InvokeRequired) { try { Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData); } catch (InvalidOperationException ex) // pump died before we were processed { if (this.IsHandleCreated) throw; // not the droids we are looking for } } else { this.MyUpdate(e.MyData); } } // the update function void MyUpdate(Object myData) { ... }

El filtro de excepción se puede adaptar para adaptarse a cualquier necesidad. Es bueno tener en cuenta que, en la mayoría de las aplicaciones, los subprocesos de los trabajadores a menudo no cuentan con el manejo y el registro de las excepciones externas acojinadas que tienen los subprocesos de la interfaz de usuario, por lo que es posible que desee engullir cualquier excepción del lado del trabajador. O inicia sesión y vuelve a lanzarlas todas. Para muchas, las excepciones no detectadas en el subproceso de trabajo significa que la aplicación se bloqueará.


La solución propuesta por Isak Savo.

try { myForm.Invoke(myForm.myDelegate, new Object[] { message }); } catch (ObjectDisposedException) { //catch exception if the owner window is already closed }

funciona en C # 4.0, pero por alguna razón falla en C # 3.0 (la excepción se produce de todos modos)

Así que utilicé otra solución basada en una bandera que indica si el formulario se está cerrando y, en consecuencia, evito el uso de invocar si la bandera está activada.

public partial class Form1 : Form { bool _closing; public bool closing { get { return _closing; } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { _closing = true; } ... // part executing in another thread: if (_owner.closing == false) { // the invoke is skipped if the form is closing myForm.Invoke(myForm.myDelegate, new Object[] { message }); }

Esto tiene la ventaja de evitar completamente el uso de try / catch.


La sugerencia de detener el hilo que genera los mensajes no es aceptable. Los delegados pueden ser multicast. Debido a que un oyente no quiere escuchar a la banda, no le disparas a los miembros de la banda. Como el marco no me proporciona ninguna forma fácil de borrar el mensaje de esos mensajes de eventos, y como el formulario no expone su propiedad privada, eso nos permite saber que el formulario se está cerrando: establezca una marca en el evento de cierre de información de la ventana después de anular la suscripción o dejar de escuchar los eventos, y siempre marque esta bandera antes de hacer esto.Invocar ().


Lo que tienes aquí es una condición de carrera . Es mejor que atrapes la excepción ObjectDisposed y que termines con ella. De hecho, creo que en este caso es la única solución de trabajo.

try { if (mImageListView.InvokeRequired) mImageListView.Invoke(new YourDelegate(thisMethod)); else mImageListView.RefreshInternal(); } catch (ObjectDisposedException ex) { // Do something clever }


Manejar el evento de cierre de formulario. Verifique si el trabajo de subprocesos fuera de la interfaz de usuario todavía está ocurriendo; si es así, comience a desactivarlo, cancele el evento de cierre y luego vuelva a programar el cierre utilizando BeginInvoke en el control de formulario.

private void Form_FormClosing(object sender, FormClosingEventArgs e) { if (service.IsRunning) { service.Exit(); e.Cancel = true; this.BeginInvoke(new Action(() => { this.Close(); })); } }


Podrías usar mutexes.

En algún lugar al inicio del hilo:

Mutex m=new Mutex();

Entonces :

if (mImageListView != null && mImageListView.IsHandleCreated && !mImageListView.IsDisposed) { m.WaitOne(); if (mImageListView.InvokeRequired) mImageListView.Invoke( new RefreshDelegateInternal(mImageListView.RefreshInternal)); else mImageListView.RefreshInternal(); m.ReleaseMutex(); }

Y donde sea que esté desechando mImageListView:

m.WaitOne(); mImageListView.Dispose(); m.ReleaseMutex();

Esto debería garantizar que no pueda disponer e invocar al mismo tiempo.


Si un BackGroundWorker es una posibilidad, hay una manera muy simple de evitar esto:

public partial class MyForm : Form { private void InvokeViaBgw(Action action) { BGW.ReportProgress(0, action); } private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e) { if (this.IsDisposed) return; //You are on the UI thread now, so no race condition var action = (Action)e.UserState; action(); } private private void BGW_DoWork(object sender, DoWorkEventArgs e) { //Sample usage: this.InvokeViaBgw(() => MyTextBox.Text = "Foo"); } }


Tengo el mismo error. mi error ocurrió en el hilo Finalmente escribo este método:

public bool IsDisposed(Control ctrl) { if (ctrl.IsDisposed) return true; try { ctrl.Invoke(new Action(() => { })); return false; } catch (ObjectDisposedException) { return true; } }


Una forma podría ser llamar al método uno más en lugar de invocar el método ImageListView:

if (mImageListView != null && mImageListView.IsHandleCreated && !mImageListView.IsDisposed) { if (mImageListView.InvokeRequired) mImageListView.Invoke(new YourDelegate(thisMethod)); else mImageListView.RefreshInternal(); }

De esa manera se verificaría una vez más antes de llamar finalmente a RefreshInternal ().


Véase también esta pregunta:

¿Evitar los problemas de Invoke / BeginInvoke en el manejo de eventos de WinForm de hilos cruzados?

La clase de utilidad que resultó en EventHandlerForControl puede resolver este problema para las firmas de los métodos de eventos. Puede adaptar esta clase o revisar la lógica de la misma para resolver el problema.

El problema real aquí es que nobugz es correcto, ya que señala que las API dadas para las llamadas de enlaces cruzados en winforms no son seguras para subprocesos. Incluso dentro de las llamadas a InvokeRequired e Invoke / BeginInvoke, hay varias condiciones de carrera que pueden causar un comportamiento inesperado.


puede estar bloqueado (mImageListView) {...}?