I have a
BindingList<T>
which is bound to a datagridview. One property in my class takes long to calculate, so I threaded the action. After the calculation I raise the OnPropertyChanged() event to notify the grid that the value is ready.
At least, that's the theory. But since the OnPropertyChanged Method is called from a differend thread I get some weired exceptions in the OnRowPrePaint method of the grid.
Can anybody tell me how I fore the OnPropertyChanged event to be excecuted in the main thread? I can not use Form.Invoke, since the class MyClass is not aware that it runs in a Winforms application.
public class MyClass : INotifyPropertyChanged
{
public int FastMember {get;set;}
private int? slowMember;
public SlowMember
{
get
{
if (slowMember.HasValue)
return slowMember.Value;
else
{
Thread t = new Thread(getSlowMember);
t.Start();
return -1;
}
}
}
private void getSlowMember()
{
Thread.Sleep(1000);
slowMember = 5;
OnPropertyChanged("SlowMember");
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangingEventHandler eh = PropertyChanging;
if (eh != null)
{
eh(this, e);
}
}
}
People sometimes forget that the event handler is a MultiCastDelegate
and, as such, has all the information regarding each subscriber that we need to handle this situation gracefully without imposing the Invoke+Synchronization performance penalty unnecessarily. I've been using code like this for ages:
using System.ComponentModel;
// ...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
foreach (EventHandler h in handler.GetInvocationList())
{
var synch = h.Target as ISynchronizeInvoke;
if (synch != null && synch.InvokeRequired)
synch.Invoke(h, new object[] { this, e });
else
h(this, e);
}
}
}
What it does is simple, but I remember that I almost cracked my brain back then trying to find the best way to do it.
It first "grabs" the event handler on a local property to avoid any race conditions.
If the handler is not null (at lease one subscriber does exist) it prepares the event args, and then iterates through the invocation list of this multicast delegate.
The invocation list has the target property, which is the event's subscriber. If this subscriber implements ISynchronizeInvoke (all UI controls implement it) we then check its InvokeRequired property, and it is true we just Invoke it passing the delegate and parameters. Calling it this way will synchronize the call into the UI thread.
Otherwise we simply call the event handler delegate directly.
By design, a control can only be updated by the thread it was created in. This is why you are getting exceptions.
Consider using a BackgroundWorker and only update the member after the long lasting operation has completed by subscribing an eventhandler to RunWorkerCompleted
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With