Jeg har jo klaget min nød igennem den sidste tid, med hensyn til kryds-tråds opdatering i WinForms, og det ser ud til at jeg, et langt stykke hen ad vejen, kan klare det med mine tidligere omtalte extension metoder. Nu viser det sig dog at jeg har større og mere generelle problemer, som jeg meget gerne vil have løst een gang for alle (hvem vil ikke det).
Det seneste skud på stammen af problemer i relation til trådproblemer, er ObjectDataSource: Jeg lader mine datakilder / modeller implementerer INotifyPropertyChanged, og binder så egenskaber på mine kontroller til forskellige medlemmer på datakilden. Det virker super fint – så længe kilden ikke har asynkrone processer der rejser PropertyChanged eventen. For så sker der bare ikke noget som helst. Jeg måtte isolerer problemet, og fandt ud af at der var tale om endnu et krydstråds problem – jeg vidste det!
I dokumentationen på MSDN kan jeg så efterfølgende læse at;
If your data source implements the INotifyPropertyChanged and you are performing asynchronous operations you should not make changes to the data source on a background thread. Instead, you should read the data on a background thread and merge the data into a list on the UI thread.
Altså på dansk: Glem alt om at bruge vores smarte databindings-mekanisme, hvis du laver opdateringer fra en baggrundstråd – det understøtter vi ikke…
Det synes jeg nu er noget bavl, for een af hovedideerne med at bruge Observer-pattern, er jo at adskille ansvar, og når man forlanger at baggrundstråden skal vide detaljer om UI trådens kapacitet og mangel på samme, så er det jo efter min mening: Sammenblanding af ansvar.
Nå men man kommer jo ikke nogen steder ved at være sur – bare videre med at kode…
Der er sikkert flere andre måder at komme uden om problemet på, og hvis jeg kunne løse min aktuelle opgave i WPF, var det sikkert også meget nemmere.
Min løsning: Tager udgangspunkt i mine tidligere omtalte extension metoder, og så forsøger jeg så at lave dem lidt mere generiske. I sin enkelthed, gælder det jo bare om at hooke op på en event og så opdaterer kontrollen efterfølgende.
Jeg bruger Proxy-pattern og laver en statisk metode, der kan udfører magien. I den simpleste form, skal proxyen altså virke på et target-objekt der implementerer ISyncronizeInvoke og et kilde-objekt der implementerer INotifyPropertyChange. Den ene rejser events, og den anden kan man ‘invoke’ metoder på:
public class BindingProxy
{
private INotifyPropertyChanged _sourc;
private string _member;
private ISynchronizeInvoke _target;
private Action SetValue;
public static void Bind(ISynchronizeInvoke target, string property, INotifyPropertyChanged source, string member)
{
new BindingProxy(target, property, source, member);
}
private BindingProxy(ISynchronizeInvoke target, string property, INotifyPropertyChanged source, string member)
{
_target = target;
var targetProperty = TypeDescriptor.GetProperties(_target)[property];
_sourc = source;
_member = member;
var sourceMember = TypeDescriptor.GetProperties(_sourc)[_member];
// Her bruger vi lidt closure for at cache TypeDescriptor’ne
SetValue = (t, s) => targetProperty.SetValue(t, sourceMember.GetValue(s));
_sourc.PropertyChanged += sourc_PropertyChanged;
}
void sourc_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != _member)
return;
if (_target.InvokeRequired)
_target.BeginInvoke(SetValue, new object[] { _target, _sourc });
else
SetValue(_target, _sourc);
}
}
Stadig en ret simpel konstruktion, som man selvølgelig lige skal lave lidt fejl-check/håndtering på, men ellers klar til databinding:
BindingProxy.Bind(button1, "Enabled", model, "IsConnected");
Voilla – trådsikker databinding, og så overholder man SoC princippet – undre mig bare at MS ikke har håndteret det i første omgang.
Code on…