C# DataBinding / 从另一个线程更新 DynamicObject 属性

C# DataBinding / Update DynamicObject property from another thread

之前我正在研究 DynamicObject 数据绑定的实现(参见 ),该解决方案效果很好。从那时起,我 运行 需要能够更新来自不同线程的值,这似乎打破了绑定。我能够从多个线程更新 DynamicObject 实例但是我无法保持绑定更新。

我尝试在 SO 中实施 Cody Barnes 提供的 SynchronizedNotifyPropertyChanged 解决方案:INotifyPropertyChanged causes cross-thread error 但没有成功。

任何有关绑定通过非 ui 线程更新的 DynamicObject 实现的帮助将不胜感激。解决方案和附加的要点很好用(在任何线程(ui 或非 ui)上更新值没问题),只是表单控件的数据绑定不是。

编辑 #1 -(感谢 Reza Aghaei)

我发现我的问题哪里有点含糊,这是我要实现的实现和目标。

首先,我有处理创建逻辑 'engine' 的表单,它可以 运行 任务(基于内部 class 方法以及外部方法调用或事件) .因此,在表单 class 的某处,我生成了这个逻辑 'engine' 对象(在本例中将其称为 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 class 中,我实例化 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 在其下面的回答中提供的内容的精确副本。在博客 post 和提供的要点中,我倾向于提供的 'Option 1',因为我希望 DynamicData 对象 (MyCustomObject) 能够自包含同步逻辑以实现可移植性。

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

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

参考:

I tried to implement the SynchronizedNotifyPropertyChanged solution provided by Cody Barnes in the SO: INotifyPropertyChanged causes cross-thread error with no success.

在使用原始 SO 和原始解决方案(再次感谢 Reza)时,我尝试实施引用的 'wrapper' 以克服跨线程更新的问题。但是,使上述解决方案起作用的唯一方法是将属性硬编码到 class 中。当尝试使用动态对象时,绑定将更新一次并丢失绑定或抛出某种绑定异常。

我希望这能更好地澄清原来的问题。

编辑 #3 - 符号未解析

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

在'MyCustomObject'中:

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'.

但是代码确实编译没有错误所以我不确定why/when我确实开始看到这个。

编辑 #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 已经解决,编辑 3 中的奇怪行为此时将由 JetBrains/Resharper 团队而不是在这里更好地处理。

编辑 #5 - 特别感谢

我希望这不是不合适的(如果是的话,管理员可以随意 delete/edit 这个),但是,我想特别感谢 Reza Aghaei 感谢您在这里和后台提供的所有帮助。 Reza,你的博客、要点和其他解决这个持续问题的帮助确实有助于解决问题并帮助我理解解决方案背后的原因。

要以更新 UI 的方式从 non-UI 线程引发 OnPropertyChanged,您需要将 ISynchronizeInvoke 的实例传递给 ICustomTypeDescriptor:

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

然后在 OnPropertyChanged 上使用 ISynchronizeInvokeInvokeInvokeRequired:

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

这样,事件将在需要时在 UI 线程中引发。