C# DataBinding / Обновить свойство DynamicObject из другого потока
Ранее я работал над реализацией привязки данных DynamicObject (см. Двусторонняя привязка данных к динамическим объектам), и решение отлично работает. С тех пор я столкнулся с необходимостью иметь возможность обновлять значения из разных потоков, что, кажется, нарушает привязки. Я могу обновить экземпляр DynamicObject из нескольких потоков, но не могу обновлять привязки.
Я попытался реализовать решение SynchronizedNotifyPropertyChanged, предоставленное Коди Барнсом в SO: INotifyPropertyChanged, приводит к межпотоковой ошибке, но безуспешно.
Будем весьма благодарны за любую помощь в связывании реализации DynamicObject, которая обновляется через не-пользовательский поток. Решение и сущность приложения работают отлично (значения обновляются в любом потоке (пользовательский или не-пользовательский интерфейс), нет проблем), это просто привязка данных к элементу управления формой, что не так.
Edit # 1 - (Спасибо Реза Агаи)
Я вижу, где мой вопрос немного расплывчатый, вот реализация и цель, которую я пытаюсь достичь.
Во-первых, у меня есть форма, которая обрабатывает создание логического "движка", который может выполнять задачи (на основе внутренних методов класса, а также внешних вызовов методов или событий). Итак, где-то внутри класса Form, я генерирую этот логический объект '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 я создаю экземпляр 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 (в настоящее время) является точной копией того, что Реза предоставил в своей сути в своем ответе ниже. В сообщении блога и в его сути я склоняюсь к предоставленному "Варианту 1", так как я хотел бы, чтобы объект DynamicData (MyCustomObject) сам содержал логику для синхронизации в целях переносимости.
Еще одно замечание: GameEngine, вне удержания объекта DynamicData в свойстве, не должен заботиться о том, использует ли его поток пользовательского интерфейса, ответственность за синхронизацию должна (на мой взгляд) оставаться за потоком пользовательского интерфейса. При этом объект DynamicData должен знать о многопоточных вызовах и обрабатывать их соответствующим образом по мере необходимости.
Правка № 2 - Обновление оригинальной ссылки на вопрос
В отношении:
Я попытался реализовать решение SynchronizedNotifyPropertyChanged, предоставленное Коди Барнсом в SO: INotifyPropertyChanged, приводит к межпотоковой ошибке, но безуспешно.
При работе с исходным объектом SO Dynamic, двусторонней привязкой данных и оригинальным решением (еще раз спасибо 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
Понятия не имею, что вызвало проблему в Edit 3, но она стала показывать другие ошибки (таинственно), такие как Cannot apply indexing with to an expression of type 'System.Collections.Generic.Dictionary'
Однако я сделал Build -> Clean Solution
а затем закрыл решение и снова открыл его в Visual Studio, и проблема, казалось, исчезла с подсветкой этой ошибки в редакторе (что я видел странное поведение, так как я начал играть с Resharper (EAP), так что, возможно, ранние ошибки доступа - но эта проблема не связана с этим SO, исходный SO был решен, и странное поведение в edit 3 будет лучше обрабатываться командой JetBrains/Resharper, а не здесь, на данном этапе.
Редактировать № 5 - Особая благодарность
Я надеюсь, что это не является неуместным (если это так, администраторы могут свободно удалять / редактировать это), однако, я хотел бы отправить отдельное спасибо Резе Агаи за всю вашу помощь как здесь, так и в фоновом режиме. Реза, ваш блог, специалисты и другая помощь в решении этой постоянной проблемы действительно помогли как решить проблему, так и помочь мне понять причины, лежащие в основе решения.
1 ответ
Поднимать OnPropertyChanged
из потока, не являющегося пользовательским интерфейсом, таким образом, который обновляет пользовательский интерфейс, вам необходимо передать экземпляр ISynchronizeInvoke
к вашей реализации ICustomTypeDescriptor
:
ISynchronizeInvoke syncronzeInvoke;
public MyCustomObject(ISynchronizeInvoke value = null)
{
syncronzeInvoke = value;
}
Затем на OnPropertyChanged
использовать это ISynchronizeInvoke
в Invoke
когда 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));
}
}
Таким образом, событие будет вызываться в потоке пользовательского интерфейса при необходимости.