1

以前我正在研究 DynamicObject 数据绑定的实现(请参阅Dynamic object two way data binding),并且该解决方案效果很好。从那以后,我遇到了能够更新来自不同线程的值的需要,这似乎破坏了绑定。我能够从多个线程更新 DynamicObject 实例,但是无法保持绑定更新。

我尝试在 SO: INotifyPropertyChanged 中实现 Cody Barnes 提供的 SynchronizedNotifyPropertyChanged 解决方案,导致跨线程错误没有成功。

对于绑定通过非 ui 线程更新的 DynamicObject 实现的任何帮助将不胜感激。附加的解决方案和要点效果很好(在任何线程(ui 或非 ui)上更新的值没问题),只是 DataBinding 到 Form Control 不是。

编辑 #1 - (谢谢 Reza Aghaei)

我明白我的问题在哪里有点模糊,这是我想要完成的实施和目标。

首先,我有处理创建可以运行任务的逻辑“引擎”的表单(基于内部类方法以及外部方法调用或事件)。因此,在 Form 类的某个地方,我生成了这个逻辑“引擎”对象(在本例中将其称为 GameEngine)。

// GameEngine initializes and provides the DynamicData (MyCustomObject)
// --> engine is defined via public or private field of (this) Form class
engine = new GameEngine();
// Create the binding
LabelTestCounter.Databindings.Add("Text", engine.Data, "TestValue", true, DataSourceUpdateMode.OnPropertyChanged);

现在在 GameEngine 类中,我实例化了 DynamicData (MyCustomObject)

public class GameEngine
{
    public dynamic Data;

    // Constructor
    public GameEngine()
    {
        Data = new MyCustomObject();
        // I would like to initialize the data here as ownership really
        // belongs to the 'GameEngine' object, not the Form
        engine.Data.TestValue = "Initial test value";
    }

    public void StartBusinessLogic()
    {
        // In reality, this would be a long-running loop that updates 
        // multiple values and performs business logic.
        Task.Run(() => {
            data.TestValue = "Another Text";
        });
    }
}

请注意,在上面的示例中,MyCustomObject 是(当前)Reza 在他的回答中提供的内容的精确副本。在博客文章和提供的要点中,我倾向于提供的“选项 1”,因为我希望 DynamicData 对象 (MyCustomObject) 能够自包含用于可移植性目的的同步逻辑。

另一个注意事项,除了将 DynamicData 对象保存在属性中之外,GameEngine 不应该关心 UI 线程是否正在使用它,同步的责任(在我看来)应该由 UI 线程承担。话虽如此,DynamicData 对象应该知道跨线程调用并根据需要相应地处理它们。

编辑 #2 - 原始问题参考更新

在参照:

我尝试在 SO: INotifyPropertyChanged 中实现 Cody Barnes 提供的 SynchronizedNotifyPropertyChanged 解决方案,导致跨线程错误没有成功。

在使用原始 SO动态对象双向数据绑定和原始解决方案(再次感谢 Reza)时,我尝试实现引用的“包装器”以克服跨线程更新的问题。但是,使上述解决方案起作用的唯一方法是将属性硬编码到类中。当尝试使用动态对象时,绑定要么更新一次并丢失绑定,要么抛出某种绑定异常。

我希望这能更好地澄清最初的问题。

编辑 #3 - 符号未解析

不确定这是否是一个问题,因为编译正在工作,(这可能是 Resharper,不确定,因为我以前不记得这个问题)。

在“我的自定义对象”中:

public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
    var properties = new List<MyCustomPropertyDescriptor>();
    foreach (var e in dictionary)
        properties.Add(new MyCustomPropertyDescriptor(e.Key, (e.Value?.GetType()) ?? typeof(object)));
    return new PropertyDescriptorCollection(properties.ToArray());
}

(e.Value?.GetType()) <-- GetType is showing 'Cannot resolve symbol 'GetType'.

但是代码确实编译没有错误,所以我不确定我为什么/何时开始看到这个。

编辑 #4 - 已解决的编辑 #3

不知道是什么导致了 Edit 3 中的问题,但它发展成为显示其他错误(神秘地),例如Cannot apply indexing with to an expression of type 'System.Collections.Generic.Dictionary',但是,我做了一个Build -> Clean Solution然后关闭解决方案并在 Visual Studio 中重新打开它,问题似乎随着该错误突出显示而消失了在编辑器中(自从我开始使用 Resharper (EAP) 以来,我已经看到了一些类似的奇怪行为,所以可能是早期访问错误?但这个问题与这个 SO 无关,原来的 SO 已经解决并且奇怪JetBrains/Resharper Team 将更好地处理编辑 3 中的行为,而不是此时。

编辑#5 - 特别感谢

我希望这不是不合适的(如果是,管理员可以随意删除/编辑它),但是,我要特别感谢Reza Aghaei在 SO 和后台的所有帮助。Reza,您的博客、要点和其他有关此持续问题的帮助确实有助于解决问题并帮助我了解解决方案背后的原因。

4

1 回答 1

3

To raise OnPropertyChanged from a non-UI thread in a way which updates the UI, you need to pass an instance of ISynchronizeInvoke to your implementation of ICustomTypeDescriptor:

ISynchronizeInvoke syncronzeInvoke;
public MyCustomObject(ISynchronizeInvoke value = null)
{
    syncronzeInvoke = value;
}

Then on OnPropertyChanged use that ISynchronizeInvoke to Invoke when InvokeRequired:

private void OnPropertyChanged(string name)
{
    var handler = PropertyChanged;
    if (handler != null)
    {
        if (syncronzeInvoke != null && syncronzeInvoke.InvokeRequired)
            syncronzeInvoke.Invoke(handler, new object[] 
                { this, new PropertyChangedEventArgs(name) });
        else
            handler(this, new PropertyChangedEventArgs(name));
    }
}

This way, the event will raise in UI thread when needed.

于 2018-04-03T19:08:14.433 回答