Event Leapfrogging

(Thanks to Sam Bent from the WPF team for identifying and giving a name to the problem)

Event Leapfrogging is an issue that arises from a combination of two inherent aspects of .NET events: multicasting and synchronous handler execution. A common place that it shows up is in property change handlers in WPF controls and INotifyPropertyChanged objects.

Here’s some code demonstrating the problem:

    public class Leapfrog : INotifyPropertyChanged
    {
        private IEnumerable<string> _currentPond;
        public IEnumerable<string> CurrentPond
        {
            get { return _currentPond; }
            set
            {
                if (_currentPond == value)
                    return;
                _currentPond = value;
                NotifyPropertyChanged("CurrentPond");
            }
        }

        private string _selectedLilyPad;
        public string SelectedLilyPad
        {
            get { return _selectedLilyPad; }
            set
            {
                if (_selectedLilyPad == value)
                    return;
                _selectedLilyPad = value;
                NotifyPropertyChanged("SelectedLilyPad");
            }
        }

        private int _pondJumps;
        public int PondJumps
        {
            get { return _pondJumps; }
            set
            {
                if (_pondJumps == value)
                    return;
                _pondJumps = value;
                NotifyPropertyChanged("PondJumps");
            }
        }

        public Leapfrog()
        {
            PropertyChanged += (sender, e) =>
            {
                if (e.PropertyName == "CurrentPond")
                {
                    SelectedLilyPad = CurrentPond.First();
                    PondJumps = 0;
                }
                if (e.PropertyName == "SelectedLilyPad")
                {
                    PondJumps++;
                }
            };
        }

        public virtual void NotifyPropertyChanged(string propertyName)
        {
            PropertyChangedEventArgs ea = new PropertyChangedEventArgs(propertyName);
            if (PropertyChanged != null)
                PropertyChanged(this, ea);
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

Leapfrog keeps a count of how many jumps to different lily pads have been taken and resets both the pad and the count when the pond is changed. Everything is ok so far, but now we need to use a few of these properties from another class. The client is going to keep an array of the jump counts for each pond it uses and simulate Data Binding by using PropertyChanged to update those counts based on which pond is currently active:

        static readonly string[] PondA = new[] { "1", "2", "3", "4" };
        static readonly string[] PondB = new[] { "A", "B", "C", "D" };

        private static void HopAround()
        {
            int pondIndex = 0;
            var pondJumpCounts = new[] { 0, 0 };
            Leapfrog frog = new Leapfrog();

            frog.PropertyChanged += (sender, e) =>
            {
                if (e.PropertyName == "CurrentPond")
                    pondIndex = frog.CurrentPond == PondA ? 0 : 1;

                if (e.PropertyName == "PondJumps")
                    pondJumpCounts[pondIndex] = frog.PondJumps;
            };

            frog.CurrentPond = PondA;
            PrintCounts(pondJumpCounts);

            frog.SelectedLilyPad = PondA[1];
            PrintCounts(pondJumpCounts);

            frog.SelectedLilyPad = PondA[2];
            PrintCounts(pondJumpCounts);

            frog.SelectedLilyPad = PondA[3];
            PrintCounts(pondJumpCounts);

            frog.CurrentPond = PondB;
            PrintCounts(pondJumpCounts);

            frog.SelectedLilyPad = PondB[1];
            PrintCounts(pondJumpCounts);
        }

        private static void PrintCounts(int[] pondJumpCounts)
        {
            Debug.WriteLine(String.Format("Pond A: {0} Pond B: {1}", pondJumpCounts[0], pondJumpCounts[1]));
        }

At the end of this series of jumps we would expect to see the Pond A with 3 jumps and Pond B with 1 jump. But here’s the actual output:

Pond A: 0 Pond B: 0
Pond A: 1 Pond B: 0
Pond A: 2 Pond B: 0
Pond A: 3 Pond B: 0
Pond A: 0 Pond B: 0
Pond A: 0 Pond B: 1

So what happened? Lets look at the order of the event calls when the CurrentPond changes:

  • CurrentPond setter -> PropertyChanged “CurrentPond”
    • “CurrentPond” handler in Leapfrog
      • PondJumps setter -> PropertyChanged “PondJumps”
        • “PondJumps” handler in HopAround
    • “CurrentPond” handler in HopAround

From this we can see that HopAround’s PondJumps is called before the CurrentPond handler which causes the reset count to be applied before the pondIndex is updated, zeroing out the count for Pond A. The PondJumps change event Leapfrogged over the CurrentPond change event to end up creating what appears at first glance to be out of order execution.

One common place where this problem came up was with the relationship between the ItemsSource and the selection properties on WPF Selector controls. This specific case was fixed in .NET 4.0 by examining the chain of dependency property changes but is still something to watch out for in your own code.

Leave a Reply

Your email address will not be published. Required fields are marked *