2012-04-14 224 views
5

這是我的情況:INotifyPropertyChanged導致跨線程錯誤

我有一個GridControl綁定到BindingList。在我做的第一件事是創建一個工作線程,並直接訪問的BindingList,但這是拋出一個「檢測到跨線程操作」,所以我也跟着蒞臨指導:

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

通過克隆原BindingList到工作線程並改變它,我得到了預期的效果。但是,最近我將INotifyPropertyChanged實現爲保存到BindingList中的對象,並且我開始再次獲取錯誤。

我的猜測是,GridView仍在監聽對象的INotifyPropertyChanged。

我該如何解決這個問題?

我的類:

public class Proxy : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

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

回答

10

如果你操縱從UI線程(如工作線程)之外的UI,那麼你需要重新加入UI線程。您可以通過在UI控件上調用Invoke來完成此操作。您可以使用InvokeRequired來測試這是否是必需的。

通常使用的模式是這樣的:

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

在你的情況下,UI被操縱爲INotifyPropertyChanged結果,所以你需要確保無論您隨時修改你的實體在UI線程(使用上述技術),或使用generic asynchronous INotifyPropertyChanged helper。這是綁定物品的包裝。它使用上述技術來確保在UI線程上觸發ChangeProperty事件。

下面是Entity類的代理的一個非常粗略的示例。這可確保屬性更改事件重新加入UI線程,並保持實體本身不被修改。很顯然,你可能想要使用DynamicObject來實現這一點。

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 ....所以我需要把它放在INotifyPropertyChanged事件?我已經用我的班級代碼更新了這個問題。 – TheGateKeeper 2012-04-14 20:29:56

+0

我已更新澄清,無論是隻更改UI線程上的綁定對象,或綁定時將其包裝在助手類中。 – TheCodeKing 2012-04-14 20:48:49

+0

沒有使用這個,但它標記爲答案,因爲它提供了很多細節。 – TheGateKeeper 2012-04-14 21:00:42

1

只是爲了防止有人遇到同樣的問題......幾個小時後我設法解決了這個問題。這是我做的:

基本上問題是實現INotifyPropertyChanged的對象是生活在工作者線程中,並且這導致訪問UI線程時出現問題。

因此,我所做的就是將需要更新的對象的引用傳遞給INotifyPropertyChanged對象,然後對其使用調用。

這裏是什麼樣子:

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

從線程,我所做的就是:

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

希望這可以幫助別人!

+2

這有點髒,因爲它意味着您的業務對象具有對UI的引用。最好使用包裝類,請參閱其他答案中的鏈接。 – TheCodeKing 2012-04-14 20:50:37

+0

感謝您的更新,但我認爲您仍然需要以某種方式傳遞對象的引用。 'this.Invoke(new Action(()=> ChangeText(text)));'例如,需要'this'作爲實際的控件。 – TheGateKeeper 2012-04-14 20:59:43

+0

我對線程很陌生,所以這對我來說都是非常新的,我甚至不能理解你鏈接的代碼。我想我現在會堅持我的方式。 – TheGateKeeper 2012-04-14 21:00:18

2

我對TheGateKeeper的最終解決方案採取了類似的方法。但是我綁定了許多不同的對象。所以我需要一些更通用的東西。解決方案是創建一個實現了ICustomTypeDescriptor的包裝器。通過這種方式,我不需要爲可以在UI中顯示的所有內容創建包裝屬性。

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 
} 

然後在用戶界面,我綁定到該包裝使用類似:

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

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

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

MyViewModel具有「DirectiveText」屬性,並沒有特別考慮到線程或視圖類實現INotifyPropertyChanged。

0

I subclassed BindingList因此我可以檢查所需的Invoke。這樣我的業務對象就沒有對UI的引用。

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