来自另一个线程

时间:2018-04-02 07:14:53

标签: c# winforms data-binding dynamicobject cross-thread

以前我正在开发DynamicObject数据绑定的实现(请参阅Dynamic object two way data binding),解决方案效果很好。从那时起,我遇到了能够更新来自不同线程的值的需要,它似乎打破了绑定。我能够从多个线程更新DynamicObject实例但是无法更新绑定。

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

任何帮助绑定通过非ui线程更新的DynamicObject实现都将非常感激。解决方案和gist附加工作很好(在任何线程上更新的值(ui或非ui)都没问题),它只是Form Control的DataBinding而不是。

编辑#1 - (谢谢Reza Aghaei)

我看到我的问题有点模糊,这是我想要实现的目标和实现目标。

首先,我有一个表格来处理逻辑引擎的创建'它可以运行任务(基于内部类方法以及外部方法调用或事件)。所以在Form类中的某个地方,我生成了这个逻辑'引擎' object(在这个例子中将其称为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)能够自包含用于同步的逻辑以便于移植。

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

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

参考:

  

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

使用原始SO Dynamic object two way data binding和原始解决方案(再次感谢Reza)时,我尝试实现引用的'包装器'克服跨线程更新的问题。但是,我能够使上述解决方案工作的唯一方法是将属性硬编码到类中。当任何尝试使用动态对象时,绑定将更新一次并丢失绑定或抛出某种绑定异常。

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

编辑#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'.

然而代码编译时没有错误,因此我不确定为什么/何时我开始看到这个。

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

不知道是什么导致了编辑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 - 特别致谢

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

1 个答案:

答案 0 :(得分:1)

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

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

然后在OnPropertyChangedISynchronizeInvoke使用Invoke InvokeRequiredprivate 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)); } }

{{1}}

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