2012-04-14 12 views
5

Aquí está mi escenario:INotifyPropertyChanged produce error de cruce de hilos

Tengo un GridControl vinculado a una lista de vinculaciones. En un primer momento lo que estaba haciendo era crear un subproceso de trabajo y acceder a la BindingList directamente, pero esto estaba lanzando una "operación de la Cruz-hilo detectado", así que seguí la guía aquí:

http://www.devexpress.com/Support/Center/p/AK2981.aspx

Por clonar el original BindingList en el hilo del trabajador y cambiando ese, obtuve el efecto deseado. Sin embargo, recientemente implementé INotifyPropertyChanged en el objeto que está en BindingList, y empecé a recibir el error nuevamente.

Supongo que el GridView todavía está escuchando el INotifyPropertyChanged del objeto.

¿Cómo puedo solucionar esto?

Mi clase:

public class Proxy : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected void OnPropertyChanged(string name) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     { 
      handler(this, new PropertyChangedEventArgs(name)); 
     } 
    } 

Respuesta

10

Si está manipulando la interfaz de usuario desde el exterior de la rosca de la interfaz de usuario (por ejemplo, de un subproceso de trabajo), entonces usted necesita para reunirse con el hilo de interfaz de usuario. Puede hacerlo llamando al Invoke en el control de la interfaz de usuario. Puede probar si esto es requerido usando InvokeRequired.

El patrón suele utilizar es la siguiente:

public void ChangeText(string text) 
{ 
    if(this.InvokeRequired) 
    { 
     this.Invoke(new Action(() => ChangeText(text))); 
    } 
    else 
    { 
     label.Text = text; 
    } 
} 

En su caso, la interfaz de usuario está siendo manipulado como resultado de INotifyPropertyChanged, por lo que necesita para asegurarse de que sea siempre modificar su entidad en el hilo de interfaz de usuario (usando la técnica anterior), o use un generic asynchronous INotifyPropertyChanged helper. Esta es una envoltura alrededor del artículo que se enlaza. Utiliza la técnica anterior para garantizar que el evento ChangeProperty se desencadene en el subproceso de interfaz de usuario.

Aquí hay un ejemplo muy cruda de un proxy para una clase Entity. Esto garantiza que el evento de cambio de propiedad vuelva a unirse al subproceso de la interfaz de usuario, y mantiene la entidad en sí misma sin modificaciones. Obviamente, es probable que desee implementar esto más genéricamente utilizando DynamicObject, por ejemplo.

public class NotificationHelper : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    private readonly ISynchronizeInvoke invokeDelegate; 
    private readonly Entity entity; 

    public NotificationHelper(ISynchronizeInvoke invokeDelegate, Entity entity) 
    { 
     this.invokeDelegate = invokeDelegate; 
     this.entity = entity; 

     entity.PropertyChanged += OnPropertyChanged; 
    } 

    public string Name 
    { 
     get { return entity.Name; } 
    } 

    private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (PropertyChanged != null) 
     { 
      if (invokeDelegate.InvokeRequired) 
      { 
       invokeDelegate.Invoke(new PropertyChangedEventHandler(OnPropertyChanged), 
            new[] { sender, e }); 
       return; 
      } 
      PropertyChanged(this, e); 
     } 
    } 
} 
+0

hmm .... ¿Debo colocar esto en el evento INotifyPropertyChanged? He actualizado la pregunta con mi código de clase. – TheGateKeeper

+0

He actualizado para aclarar, ya sea que solo cambie el objeto enlazado en el subproceso de la interfaz de usuario, o que lo ajuste en una clase de ayuda cuando se enlace. – TheCodeKing

+0

No usé esto, pero lo marqué como respuesta, ya que proporciona muchos detalles. – TheGateKeeper

1

Por si acaso alguien se ha encontrado con el mismo problema ... logré solucionarlo después de algunas horas. Esto es lo que hice:

Básicamente el problema era que el objeto que implementaba INotifyPropertyChanged estaba viviendo en un subproceso de trabajo, y esto causa problemas al acceder al subproceso de la interfaz de usuario.

Lo que hice fue pasar una referencia al objeto que necesita actualizarse al objeto INotifyPropertyChanged, y luego usar invoke sobre él.

Esto es lo que parece:

public event PropertyChangedEventHandler PropertyChanged; 

    protected void OnPropertyChanged(string name) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     { 
      //If the Proxy object is living in a non-UI thread, use invoke 
      if (c != null) 
      { 
       c.BeginInvoke(new Action(() => handler(this, new PropertyChangedEventArgs(name)))); 
      } 
      //Otherwise update directly 
      else 
      { 
       handler(this, new PropertyChangedEventArgs(name)); 
      } 

     } 
    } 

    //Use this to reference the object on the UI thread when there is need to 
    public Control C 
    { 
     set { c = value; } 
    } 

Desde el hilo, todo lo que hice fue:

    prox.c = this; 
        //Logic here 
        prox.c = null; 

Espero que esto ayude a alguien !!

+2

Esto es un poco sucio ya que significa que su objeto comercial tiene referencias a la IU. Es mejor usar una clase contenedora, vea el enlace en la otra respuesta. – TheCodeKing

+0

Gracias por la actualización, pero creo que todavía necesita pasar una referencia al objeto de una forma u otra. 'this.Invoke (new Action (() => ChangeText (text)));' por ejemplo, necesitaría 'this' para ser el control real. – TheGateKeeper

+0

Soy muy nuevo en el roscado, por lo que todo esto es muy nuevo para mí, ni siquiera puedo entender el código que vinculó. Creo que me mantendré en mi camino por ahora. – TheGateKeeper

2

Tomé un enfoque similar a la solución final de TheGateKeeper. Sin embargo, estaba vinculando a muchos objetos diferentes. Así que necesitaba algo un poco más genérico.La solución fue crear un contenedor que implementara también ICustomTypeDescriptor. De esta manera, no es necesario crear propiedades de contenedor para todo lo que se puede mostrar en la interfaz de usuario.

public class SynchronizedNotifyPropertyChanged<T> : INotifyPropertyChanged, ICustomTypeDescriptor 
    where T : INotifyPropertyChanged 
{ 
    private readonly T _source; 
    private readonly ISynchronizeInvoke _syncObject; 

    public SynchronizedNotifyPropertyChanged(T source, ISynchronizeInvoke syncObject) 
    { 
     _source = source; 
     _syncObject = syncObject; 

     _source.PropertyChanged += (sender, args) => OnPropertyChanged(args.PropertyName); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     if (PropertyChanged == null) return; 

     var handler = PropertyChanged; 
     _syncObject.BeginInvoke(handler, new object[] { this, new PropertyChangedEventArgs(propertyName) }); 
    } 

    public T Source { get { return _source; }} 

    #region ICustomTypeDescriptor 
    public AttributeCollection GetAttributes() 
    { 
     return new AttributeCollection(null); 
    } 

    public string GetClassName() 
    { 
     return TypeDescriptor.GetClassName(typeof(T)); 
    } 

    public string GetComponentName() 
    { 
     return TypeDescriptor.GetComponentName(typeof (T)); 
    } 

    public TypeConverter GetConverter() 
    { 
     return TypeDescriptor.GetConverter(typeof (T)); 
    } 

    public EventDescriptor GetDefaultEvent() 
    { 
     return TypeDescriptor.GetDefaultEvent(typeof (T)); 
    } 

    public PropertyDescriptor GetDefaultProperty() 
    { 
     return TypeDescriptor.GetDefaultProperty(typeof(T)); 
    } 

    public object GetEditor(Type editorBaseType) 
    { 
     return TypeDescriptor.GetEditor(typeof (T), editorBaseType); 
    } 

    public EventDescriptorCollection GetEvents() 
    { 
     return TypeDescriptor.GetEvents(typeof(T)); 
    } 

    public EventDescriptorCollection GetEvents(Attribute[] attributes) 
    { 
     return TypeDescriptor.GetEvents(typeof (T), attributes); 
    } 

    public PropertyDescriptorCollection GetProperties() 
    { 
     return TypeDescriptor.GetProperties(typeof (T)); 
    } 

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes) 
    { 
     return TypeDescriptor.GetProperties(typeof(T), attributes); 
    } 

    public object GetPropertyOwner(PropertyDescriptor pd) 
    { 
     return _source; 
    } 
    #endregion ICustomTypeDescriptor 
} 

Luego, en el Ui, que se unen a este contenedor usando algo como:

private void CreateBindings() 
    { 
     if (_model == null) return; 

     var threadSafeModel = new SynchronizedNotifyPropertyChanged<MyViewModel>(_model, this); 

     directiveLabel.DataBindings.Add("Text", threadSafeModel, "DirectiveText", false, DataSourceUpdateMode.OnPropertyChanged); 
    } 

MyViewModel tiene una propiedad "DirectiveText" e implementa INotifyPropertyChanged sin ninguna consideración especial para roscar o para las clases de vista.

0

He subclasificado BindingList para poder comprobar si es necesario Invoke. De esta forma, mis objetos comerciales no tienen una referencia a la IU.

public class InvokingBindingList<T> : BindingList<T> 
{ 
    public InvokingBindingList(IList<T> list, Control control = null) : base(list) 
    { 
    this.Control = control; 
    } 

    public InvokingBindingList(Control control = null) 
    { 
    this.Control = control; 
    } 

    public Control Control { get; set; } 

    protected override void OnListChanged(ListChangedEventArgs e) 
    { 
    if (Control?.InvokeRequired == true) 
     Control.Invoke(new Action(() => base.OnListChanged(e))); 
    else 
     base.OnListChanged(e); 
    } 
} 
Cuestiones relacionadas