Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

INotifyPropertyChanged with threads

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

}
like image 317
Jürgen Steinblock Avatar asked Dec 10 '09 10:12

Jürgen Steinblock


2 Answers

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.

like image 74
Loudenvier Avatar answered Oct 02 '22 14:10

Loudenvier


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.

like image 26
Yannick Motton Avatar answered Oct 02 '22 13:10

Yannick Motton