until the que puede llamar identificador haya hasta has created creado control cannot called been c# .net winforms multithreading events

c# - que - invoke or begininvoke cannot be called on a control until the window handle has been created



¿Evitando los problemas de Invoke/BeginInvoke en el manejo de eventos WinForm de cruce de hilos? (13)

¿Por qué no simplemente ocultar el diálogo cuando el usuario lo descarta? Eso debería funcionar bien si no muestra ese diálogo de forma modal. (use show en lugar de showdialog). Creo que puede mantener su diálogo de progreso en la parte superior de su ventana propietaria (si es necesario) pasando el host al cuadro de diálogo cuando llame al programa.

Todavía estoy plagado de subprocesos de fondo en una interfaz de usuario de WinForm. ¿Por qué? Estos son algunos de los problemas:

  1. Obviamente, el problema más importante es que no puedo modificar un Control a menos que esté ejecutando el mismo hilo que lo creó.
  2. Como ya sabe, Invoke, BeginInvoke, etc. no están disponibles hasta después de crear un Control.
  3. Incluso después de que RequiresInvoke devuelva verdadero, BeginInvoke aún puede lanzar ObjectDisposed e incluso si no lo lanza, es posible que nunca ejecute el código si el control se está destruyendo.
  4. Incluso después de que RequiresInvoke devuelva verdadero, Invoke puede colgar indefinidamente en espera de la ejecución por un control que se dispuso al mismo tiempo que la llamada a Invoke.

Estoy buscando una solución elegante para este problema, pero antes de entrar en detalles de lo que estoy buscando, pensé que aclararía el problema. Esto es tomar el problema genérico y poner un ejemplo más concreto detrás de él. Para este ejemplo, digamos que estamos transfiriendo grandes cantidades de datos a través de Internet. La interfaz de usuario debe poder mostrar un diálogo de progreso para la transferencia que ya está en progreso. El diálogo de progreso debe actualizarse de manera constante y rápida (actualizaciones de 5 a 20 veces por segundo). El usuario puede cerrar el cuadro de diálogo de progreso en cualquier momento y recuperarlo nuevamente si lo desea. Y además, imaginemos por argumentos que, si el diálogo es visible, debe procesar cada evento de progreso. El usuario puede hacer clic en Cancelar en el diálogo de progreso y, mediante la modificación de los argumentos del evento, cancelar la operación.

Ahora necesito una solución que se ajuste a la siguiente caja de restricciones:

  1. Permita que un subproceso de trabajo llame a un método en un Control / Form y bloquee / espere hasta que se complete la ejecución.
  2. Permita que el diálogo llame a este mismo método en la inicialización o similar (y, por lo tanto, no use invoke).
  3. No imponga ninguna carga de implementación en el método de manejo o el evento de llamada, la solución solo debería cambiar la suscripción al evento.
  4. Maneje apropiadamente las invocaciones de bloqueo a un diálogo que podría estar en proceso de eliminación. Lamentablemente, esto no es tan fácil como verificar IsDisposed.
  5. Debe poder usarse con cualquier tipo de evento (asuma un delegado de tipo EventHandler)
  6. No debe traducir excepciones a TargetInvocationException.
  7. La solución debe funcionar con .Net 2.0 y superior

Entonces, ¿se puede resolver esto dadas las restricciones anteriores? He buscado y cavado a través de innumerables blogs y debates y, lamentablemente, todavía estoy con las manos vacías.

Actualización: me doy cuenta de que esta pregunta no tiene una respuesta fácil. Solo he estado en este sitio durante un par de días y he visto a algunas personas con mucha experiencia respondiendo preguntas. Espero que uno de estos individuos haya resuelto esto lo suficiente como para que no pase la semana o así que tarde en construir una solución razonable.

Actualización n. ° 2: Bien, voy a tratar de describir el problema con un poco más de detalle y ver qué pasa (en todo caso). Las siguientes propiedades que nos permiten determinar su estado tienen un par de cosas que generan inquietudes ...

  1. Control.InvokeRequired = Documentado para devolver falso si se ejecuta en el hilo actual o si IsHandleCreated devuelve falso para todos los padres. Me preocupa que la implementación InvokeRequired tenga el potencial de arrojar ObjectDisposedException o incluso volver a crear el identificador del objeto. Y dado que InvokeRequired puede devolver true cuando no podemos invocar (Dispose in progress) y puede devolver false aunque necesitemos usar invoke (Create in progress) esto simplemente no puede ser confiable en todos los casos. El único caso en el que puedo ver que podemos confiar en que InvokeRequired devuelve falso es cuando IsHandleCreated devuelve verdadero antes y después de la llamada (por cierto, los documentos de MSDN para InvokeRequired mencionan la comprobación de IsHandleCreated).

  2. Control.IsHandleCreated = Devuelve verdadero si se ha asignado un control al control; de lo contrario, falso. Aunque IsHandleCreated es una llamada segura, puede fallar si el control está en el proceso de volver a crear su control. Este problema potencial parece ser solucionable mediante la realización de un bloqueo (control) al acceder a IsHandleCreated y InvokeRequired.

  3. Control.Disposing = Devuelve true si el control está en proceso de eliminación.

  4. Control.IsDisposed = Devuelve true si el control ha sido eliminado. Estoy considerando suscribirme al evento Disposed y verificar la propiedad IsDisposed para determinar si BeginInvoke alguna vez se completará. El gran problema aquí es la falta de un bloqueo de sincronización durante la Disposición -> transición Dispuesta. Es posible que si te suscribes al evento Disposed y después de eso verificas que Disposing == false && IsDisposed == false aún no puedas ver el fuego del evento Disposed. Esto se debe al hecho de que la implementación de Dispose sets Disposing = false, y luego establece Disposed = true. Esto le brinda la oportunidad (por pequeña que sea) de leer tanto Disposición como IsDeposición como falso en un control dispuesto.

... me duele la cabeza :( Espero que la información de arriba arroje un poco más de luz sobre los problemas para cualquiera que tenga estos problemas. Agradezco sus ciclos de pensamiento de sobra en esto.

Acercando el problema ... La siguiente es la mitad posterior del método Control.DestroyHandle ():

if (!this.RecreatingHandle && (this.threadCallbackList != null)) { lock (this.threadCallbackList) { Exception exception = new ObjectDisposedException(base.GetType().Name); while (this.threadCallbackList.Count > 0) { ThreadMethodEntry entry = (ThreadMethodEntry) this.threadCallbackList.Dequeue(); entry.exception = exception; entry.Complete(); } } } if ((0x40 & ((int) ((long) UnsafeNativeMethods.GetWindowLong(new HandleRef(this.window, this.InternalHandle), -20)))) != 0) { UnsafeNativeMethods.DefMDIChildProc(this.InternalHandle, 0x10, IntPtr.Zero, IntPtr.Zero); } else { this.window.DestroyHandle(); }

Notará que ObjectDisposedException se envía a todas las invocaciones de hilos cruzados en espera. Poco después de esto es la llamada a this.window.DestroyHandle () que a su vez destruye la ventana y configura su referencia de control a IntPtr.Zero evitando así otras llamadas en el método BeginInvoke (o más precisamente MarshaledInvoke que maneja tanto BeginInvoke como Invoke). El problema aquí es que después de las liberaciones de bloqueo en threadCallbackList, se puede insertar una nueva entrada antes de que el subproceso del control cercene el asa de la ventana. Este parece ser el caso que estoy viendo, aunque con poca frecuencia, con la frecuencia suficiente para detener un lanzamiento.

Actualización # 4:

Perdón por seguir arrastrando esto; sin embargo, pensé que valía la pena documentarse aquí. He logrado resolver la mayoría de los problemas anteriores y me estoy acercando a una solución que funciona. He golpeado un problema más que me preocupaba, pero hasta ahora, no he visto ''en la naturaleza''.

Este problema tiene que ver con el genio que escribió la propiedad Control.Handle:

public IntPtr get_Handle() { if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired) { throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name })); } if (!this.IsHandleCreated) { this.CreateHandle(); } return this.HandleInternal; }

Esto por sí solo no es tan malo (independientemente de mis opiniones sobre las {} modificaciones get}; sin embargo, cuando se combina con la propiedad InvokeRequired o el método Invoke / BeginInvoke es malo. Aquí está el flujo básico de la invocación:

if( !this.IsHandleCreated ) throw; ... do more stuff PostMessage( this.Handle, ... );

El problema aquí es que desde otro hilo puedo pasar con éxito a través de la primera instrucción if, después de lo cual el control es destruido por el hilo del control, lo que hace que get de la propiedad Handle vuelva a crear el identificador de ventana en mi hilo. Esto puede provocar una excepción en el hilo del control original. Esta realmente me tiene perplejo ya que no hay forma de evitar esto. Si solo hubieran usado la propiedad InternalHandle y probado el resultado de IntPtr.Zero, esto no sería un problema.


Esta es una pregunta bastante difícil. Como menciono en un comentario, no creo que se pueda resolver dadas las restricciones documentadas. Puede piratearlo dada una implementación particular del framework .net: conocer la implementación de varias funciones de miembros puede ayudarlo a hacer trampas accediendo a cerraduras aquí y allá, y sabiendo que "en realidad está bien, llamar a otras funciones miembro en un hilo diferente. "

Entonces, mi respuesta básica por el momento es "no". Odio decir que no es posible porque tengo mucha fe en el framework .Net. Además, comparativamente soy novato, no he estudiado frameworks en general, o CS, pero el internet está abierto (¡incluso para personas ignorantes como yo)!

En un tema diferente, el argumento puede ser planteado y bien respaldado, "Nunca deberías necesitar Invoke , solo usar BeginInvoke , y disparar y olvidarte". No me molestaré en tratar de apoyarlo o incluso decir que es una afirmación correcta, pero diré que la implementación común es incorrecta, y plantear una que funcione (espero).

Aquí hay una implementación común (tomada de una respuesta diferente aquí):

protected override void OnLoad() { //... component.Event += new EventHandler(myHandler); } protected override void OnClosing() { //... component.Event -= new EventHandler(myHandler); }

Esto no es seguro para subprocesos. El componente podría haber comenzado fácilmente a llamar a la lista de invocación justo antes de la cancelación de la suscripción, y solo después de que terminemos la eliminación se invocará el controlador. El verdadero punto es que no está documentado cómo cada componente debe usar el mecanismo de eventos en .Net, y sinceramente, no tiene que darse de baja en absoluto: una vez que haya entregado su número de teléfono, ¡nadie debe borrarlo!

Mejor es:

protected override void OnLoad(System.EventArgs e) { component.Event += new System.EventHandler(myHandler); } protected override void OnFormClosing(FormClosedEventArgs e) { component.Event -= new System.EventHandler(myHandler); lock (lockobj) { closing = true; } } private void Handler(object a, System.EventArgs e) { lock (lockobj) { if (closing) return; this.BeginInvoke(new System.Action(HandlerImpl)); } } /*Must be called only on GUI thread*/ private void HandlerImpl() { this.Hide(); } private readonly object lockobj = new object(); private volatile bool closing = false;

Por favor, avíseme si me perdí algo.


Esta no es realmente la respuesta a la segunda parte de la pregunta, pero la incluiré solo para la referencia:

private delegate object SafeInvokeCallback(Control control, Delegate method, params object[] parameters); public static object SafeInvoke(this Control control, Delegate method, params object[] parameters) { if (control == null) throw new ArgumentNullException("control"); if (control.InvokeRequired) { IAsyncResult result = null; try { result = control.BeginInvoke(new SafeInvokeCallback(SafeInvoke), control, method, parameters); } catch (InvalidOperationException) { /* This control has not been created or was already (more likely) closed. */ } if (result != null) return control.EndInvoke(result); } else { if (!control.IsDisposed) return method.DynamicInvoke(parameters); } return null; }

Este código debería evitar los errores más comunes con Invoke / BeginInvoke y es fácil de usar. Solo voltea

if (control.InvokeRequired) control.Invoke(...) else ...

dentro

control.SafeInvoke(...)

Constructo similar es posible para BeginInvoke.


Esto es lo que estoy usando actualmente. Se basa en el uso de SynchronizationContext, y se inspiró en el artículo de blog de JaredPar, ver su respuesta anterior. Esto puede no ser perfecto, pero evita algunos de los problemas de OP que también estaba experimentando.

// Homemade Action-style delegates to provide .Net 2.0 compatibility, since .Net 2.0 does not // include a non-generic Action delegate nor Action delegates with more than one generic type // parameter. (The DMethodWithOneParameter<T> definition is not needed, could be Action<T> // instead, but is defined for consistency.) Some interesting observations can be found here: // http://geekswithblogs.net/BlackRabbitCoder/archive/2011/11/03/c.net-little-wonders-the-generic-action-delegates.aspx public delegate void DMethodWithNoParameters(); public delegate void DMethodWithOneParameter<T>(T parameter1); public delegate void DMethodWithTwoParameters<T1, T2>(T1 parameter1, T2 parameter2); public delegate void DMethodWithThreeParameters<T1, T2, T3>(T1 parameter1, T2 parameter2, T3 parameter3); /// <summary> /// Class containing support code to use the SynchronizationContext mechanism to dispatch the /// execution of a method to the WinForms UI thread, from another thread. This can be used as an /// alternative to the Control.BeginInvoke() mechanism which can be problematic under certain /// conditions. See for example the discussion here: /// http://.com/questions/1364116/avoiding-the-woes-of-invoke-begininvoke-in-cross-thread-winform-event-handling /// /// As currently coded this works with methods that take zero, one, two or three arguments, but /// it is a trivial job to extend the code for methods taking more arguments. /// </summary> public class WinFormsHelper { // An arbitrary WinForms control associated with thread 1, used to check that thread-switching // with the SynchronizationContext mechanism should be OK private readonly Control _thread1Control = null; // SynchronizationContext for the WinForms environment''s UI thread private readonly WindowsFormsSynchronizationContext _synchronizationContext; /// <summary> /// Constructor. This must be called on the WinForms UI thread, typically thread 1. (Unless /// running under the Visual Studio debugger, then the thread number is arbitrary.) /// /// The provided "thread 1 control" must be some WinForms control that will remain in /// existence for as long as this object is going to be used, for example the main Form /// control for the application. /// </summary> /// <param name="thread1Control">see above</param> public WinFormsHelper(Control thread1Control) { _thread1Control = thread1Control; if (thread1Control.InvokeRequired) throw new Exception("Not called on thread associated with WinForms controls."); _synchronizationContext = SynchronizationContext.Current as WindowsFormsSynchronizationContext; if (_synchronizationContext == null) // Should not be possible? throw new Exception("SynchronizationContext.Current = null or wrong type."); } // The following BeginInvoke() methods follow a boilerplate pattern for how these methods // should be implemented - they differ only in the number of arguments that the caller wants // to provide. public void BeginInvoke(DMethodWithNoParameters methodWithNoParameters) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithNoParameters(); }, null); } public void BeginInvoke<T>(DMethodWithOneParameter<T> methodWithOneParameter, T parameter1) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithOneParameter(parameter1); }, null); } public void BeginInvoke<T1, T2>(DMethodWithTwoParameters<T1, T2> methodWithTwoParameters, T1 parameter1, T2 parameter2) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithTwoParameters(parameter1, parameter2); }, null); } public void BeginInvoke<T1, T2, T3>(DMethodWithThreeParameters<T1, T2, T3> methodWithThreeParameters, T1 parameter1, T2 parameter2, T3 parameter3) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithThreeParameters(parameter1, parameter2, parameter3); }, null); } }


Intento organizar todos esos mensajes de invocación en la GUI como "fuego" y olvidar (manejo de la excepción que la GUI puede arrojar debido a la condición de carrera al eliminar el formulario).

De esta forma, si nunca se ejecuta, no se hace daño.

Si la GUI necesita responder al hilo de trabajo, tiene una forma de revertir eficazmente la notificación. Para necesidades simples, BackgroundWorker ya maneja esto.


Me encontré con este problema un tiempo atrás y se me ocurrió una solución que implicaba contextos de sincronización. La solución es agregar un método de extensión a SynchronizationContext que vincula a un delegado particular con el hilo al que está vinculado el SynchronizationContext. Generará un nuevo delegado que, cuando se invoque, coordinará la llamada al subproceso apropiado y luego llamará al delegado original. Hace casi imposible para los consumidores del delegado llamarlo en el contexto incorrecto.

Publicación del blog sobre el tema:


No voy a escribirle una solución exhaustiva que cumpla con todos sus requisitos, pero ofreceré una perspectiva. Sin embargo, en general, creo que estás disparando a la luna con esos requisitos.

La arquitectura Invoke / BeginInvoke simplemente ejecuta un delegado suministrado en el subproceso de IU del control enviándole un mensaje de Windows y el bucle de mensaje mismo ejecuta el delegado. El funcionamiento específico de esto es irrelevante, pero el punto es que no hay ninguna razón particular por la que deba usar esta arquitectura para la sincronización de subprocesos con el subproceso de interfaz de usuario. Todo lo que necesita es ejecutar otro bucle, como en un Forms.Timer o algo así, que supervisa una Queue para que los delegados la ejecuten y lo hace. Sería bastante sencillo implementar el suyo, aunque no sé qué específicamente le proporcionaría Invoke y BeginInvoke .


Ok, días después terminé de crear una solución. Resuelve todas las limitaciones y objetivos enumerados en la publicación inicial. El uso es simple y directo:

myWorker.SomeEvent += new EventHandlerForControl<EventArgs>(this, myWorker_SomeEvent).EventHandler;

Cuando el hilo de trabajo llama a este evento, manejará la invocación requerida al hilo de control. Asegura que no se bloqueará indefinidamente y arrojará consistentemente una ObjectDisposedException si no puede ejecutarse en el hilo de control. Creé otras derivaciones de la clase, una para ignorar el error y otra para llamar directamente al delegado si el control no está disponible. Parece que funciona bien y pasa por completo las diversas pruebas que reproducen los problemas anteriores. Solo hay un problema con la solución que no puedo evitar sin violar la restricción n.º 3 anterior. Este problema es el último (Actualización n. ° 4) en la descripción del problema, los problemas de subprocesamiento en obtener Manejar. Esto puede provocar un comportamiento inesperado en el subproceso de control original, y he visto regularmente InvalidOperationException () lanzado al llamar a Dispose () desde el identificador en el proceso de creación en mi subproceso. Para poder tratar con esto, aseguro un bloqueo para acceder a las funciones que usarán la propiedad Control.Handle. Esto permite que un formulario sobrecargue el método DestroyHandle y el bloqueo antes de llamar a la implementación base. Si esto se hace, esta clase debe ser totalmente segura para subprocesos (a mi leal saber y entender).

public class Form : System.Windows.Forms.Form { protected override void DestroyHandle() { lock (this) base.DestroyHandle(); } }

Puede observar que el aspecto central de la solución del bloqueo muerto se convirtió en un ciclo de votación. Originalmente resolví con éxito los casos de prueba manejando el evento del control para Disposed y HandleDestroyed y usando múltiples manejadores de espera. Después de una revisión más cuidadosa, descubrí que la suscripción / desuscripción de estos eventos no es segura para subprocesos. Por lo tanto, opté por sondear el IsHandleCreated en su lugar para no crear una disputa innecesaria sobre los eventos de la secuencia y así evitar la posibilidad de seguir produciendo un estado de bloqueo muerto.

De todos modos, aquí está la solución que se me ocurrió:

/// <summary> /// Provies a wrapper type around event handlers for a control that are safe to be /// used from events on another thread. If the control is not valid at the time the /// delegate is called an exception of type ObjectDisposedExcpetion will be raised. /// </summary> [System.Diagnostics.DebuggerNonUserCode] public class EventHandlerForControl<TEventArgs> where TEventArgs : EventArgs { /// <summary> The control who''s thread we will use for the invoke </summary> protected readonly Control _control; /// <summary> The delegate to invoke on the control </summary> protected readonly EventHandler<TEventArgs> _delegate; /// <summary> /// Constructs an EventHandler for the specified method on the given control instance. /// </summary> public EventHandlerForControl(Control control, EventHandler<TEventArgs> handler) { if (control == null) throw new ArgumentNullException("control"); _control = control.TopLevelControl; if (handler == null) throw new ArgumentNullException("handler"); _delegate = handler; } /// <summary> /// Constructs an EventHandler for the specified delegate converting it to the expected /// EventHandler&lt;TEventArgs> delegate type. /// </summary> public EventHandlerForControl(Control control, Delegate handler) { if (control == null) throw new ArgumentNullException("control"); _control = control.TopLevelControl; if (handler == null) throw new ArgumentNullException("handler"); //_delegate = handler.Convert<EventHandler<TEventArgs>>(); _delegate = handler as EventHandler<TEventArgs>; if (_delegate == null) { foreach (Delegate d in handler.GetInvocationList()) { _delegate = (EventHandler<TEventArgs>) Delegate.Combine(_delegate, Delegate.CreateDelegate(typeof(EventHandler<TEventArgs>), d.Target, d.Method, true) ); } } if (_delegate == null) throw new ArgumentNullException("_delegate"); } /// <summary> /// Used to handle the condition that a control''s handle is not currently available. This /// can either be before construction or after being disposed. /// </summary> protected virtual void OnControlDisposed(object sender, TEventArgs args) { throw new ObjectDisposedException(_control.GetType().Name); } /// <summary> /// This object will allow an implicit cast to the EventHandler&lt;T> type for easier use. /// </summary> public static implicit operator EventHandler<TEventArgs>(EventHandlerForControl<TEventArgs> instance) { return instance.EventHandler; } /// <summary> /// Handles the ''magic'' of safely invoking the delegate on the control without producing /// a dead-lock. /// </summary> public void EventHandler(object sender, TEventArgs args) { bool requiresInvoke = false, hasHandle = false; try { lock (_control) // locked to avoid conflicts with RecreateHandle and DestroyHandle { if (true == (hasHandle = _control.IsHandleCreated)) { requiresInvoke = _control.InvokeRequired; // must remain true for InvokeRequired to be dependable hasHandle &= _control.IsHandleCreated; } } } catch (ObjectDisposedException) { requiresInvoke = hasHandle = false; } if (!requiresInvoke && hasHandle) // control is from the current thread { _delegate(sender, args); return; } else if (hasHandle) // control invoke *might* work { MethodInvokerImpl invocation = new MethodInvokerImpl(_delegate, sender, args); IAsyncResult result = null; try { lock (_control)// locked to avoid conflicts with RecreateHandle and DestroyHandle result = _control.BeginInvoke(invocation.Invoker); } catch (InvalidOperationException) { } try { if (result != null) { WaitHandle handle = result.AsyncWaitHandle; TimeSpan interval = TimeSpan.FromSeconds(1); bool complete = false; while (!complete && (invocation.MethodRunning || _control.IsHandleCreated)) { if (invocation.MethodRunning) complete = handle.WaitOne();//no need to continue polling once running else complete = handle.WaitOne(interval); } if (complete) { _control.EndInvoke(result); return; } } } catch (ObjectDisposedException ode) { if (ode.ObjectName != _control.GetType().Name) throw;// *likely* from some other source... } } OnControlDisposed(sender, args); } /// <summary> /// The class is used to take advantage of a special-case in the Control.InvokeMarshaledCallbackDo() /// implementation that allows us to preserve the exception types that are thrown rather than doing /// a delegate.DynamicInvoke(); /// </summary> [System.Diagnostics.DebuggerNonUserCode] private class MethodInvokerImpl { readonly EventHandler<TEventArgs> _handler; readonly object _sender; readonly TEventArgs _args; private bool _received; public MethodInvokerImpl(EventHandler<TEventArgs> handler, object sender, TEventArgs args) { _received = false; _handler = handler; _sender = sender; _args = args; } public MethodInvoker Invoker { get { return this.Invoke; } } private void Invoke() { _received = true; _handler(_sender, _args); } public bool MethodRunning { get { return _received; } } } }

Si ve algo mal aquí, por favor hágamelo saber.


Si entiendo esto, ¿por qué necesita deshacerse del diálogo de progreso mientras se ejecuta la aplicación? ¿Por qué no simplemente mostrar y ocultarlo en la solicitud de los usuarios? Esto parece que hará que su problema al menos sea un poco más simple.



Su escenario, como se describe, encaja perfectamente con BackgroundWorker , ¿por qué no usarlo? Sus requisitos para una solución son demasiado genéricos, y bastante irrazonables. Dudo que haya alguna solución que los satisfaga a todos.


Usar System.ComponentModel.ISynchronizeInvoke es bueno cuando se crea un System.ComponentModel.Component , como BackgroundWorker . El siguiente fragmento de código es cómo FileSystemWater maneja los eventos.

'''''' <summary> '''''' Gets or sets the object used to marshal the event handler calls issued as a result of finding a file in a search. '''''' </summary> <IODescription(SR.FSS_SynchronizingObject), DefaultValue(CType(Nothing, String))> _ Public Property SynchronizingObject() As System.ComponentModel.ISynchronizeInvoke Get If (_synchronizingObject Is Nothing) AndAlso (MyBase.DesignMode) Then Dim oHost As IDesignerHost = DirectCast(MyBase.GetService(GetType(IDesignerHost)), IDesignerHost) If (Not (oHost Is Nothing)) Then Dim oRootComponent As Object = oHost.RootComponent If (Not (oRootComponent Is Nothing)) AndAlso (TypeOf oRootComponent Is ISynchronizeInvoke) Then _synchronizingObject = DirectCast(oRootComponent, ISynchronizeInvoke) End If End If End If Return _synchronizingObject End Get Set(ByVal Value As System.ComponentModel.ISynchronizeInvoke) _synchronizingObject = Value End Set End Property Private _onStartupHandler As EventHandler Protected Sub OnStartup(ByVal e As EventArgs) If ((Not Me.SynchronizingObject Is Nothing) AndAlso Me.SynchronizingObject.InvokeRequired) Then Me.SynchronizingObject.BeginInvoke(_onStartupHandler, New Object() {Me, e}) Else _onStartupHandler.Invoke(Me, e) End If End Sub


Wow, larga pregunta. Trataré de organizar mi respuesta para que puedas corregirme si entendí algo mal, ¿de acuerdo?

1) A menos que tenga una muy buena razón para llamar a los métodos de IU directamente desde diferentes hilos, no lo haga. Siempre puede elegir el modelo de productor / consumidor utilizando controladores de eventos:

protected override void OnLoad() { //... component.Event += new EventHandler(myHandler); } protected override void OnClosing() { //... component.Event -= new EventHandler(myHandler); }

myHandler se activará cada vez que el componente en un hilo diferente necesite realizar algo en la interfaz de usuario, por ejemplo. Además, al configurar el controlador de eventos en OnLoad y cancelar la suscripción en OnClosing, se asegura de que los eventos solo sean recibidos / manejados por la UI mientras se crea su identificador y está listo para procesar los eventos. Ni siquiera podrás disparar eventos a este diálogo si está en proceso de eliminación, porque ya no estarás suscrito al evento. Si se dispara otro evento mientras uno aún se está procesando, se pondrá en cola.

Puede pasar toda la información que necesita en los argumentos del evento: si está actualizando el progreso, cerrando la ventana, etc.

2) No necesita InvokeRequired si usa el modelo que sugerí anteriormente. En este ejemplo, usted sabe que lo único que activa myHandler será su componente que viva en otro hilo, por ejemplo.

private void myHandler(object sender, EventArgs args) { BeginInvoke(Action(myMethod)); }

Por lo tanto, siempre puedes usar invoke para asegurarte de que estarás en el hilo correcto.

3) Tenga cuidado con las llamadas sincrónicas. Si lo desea, puede reemplazar el uso de Invoke en lugar de BeginInvoke. Esto bloqueará su componente hasta que el evento haya sido procesado. Sin embargo, si en la interfaz de usuario necesita comunicarse con algo exclusivo del hilo en el que vive su componente, puede tener problemas de interbloqueo. (No sé si me dejé en claro, por favor avíseme). He tenido problemas con las excepciones cuando uso la reflexión (TargetInvocationException) y BeginInvoke (ya que comienzan un hilo diferente, pierdes parte del seguimiento de la pila), pero no recuerdo haber tenido muchos problemas con Invoke Calls, por lo que deberías estar seguro cuando se trata de excepciones.

Whoa, larga respuesta. Si por casualidad me perdí alguno de tus requisitos o entendí mal algo que dijiste (el inglés no es mi lengua materna, por lo que nunca estamos seguros), házmelo saber.