Jimmy
Jimmy

Reputation: 3264

why garbage collection, closure and lambda disabling prism event aggregator subscription?

I have a simple event defined using PRISM event aggregator pattern

 public class TestEvent : PubSubEvent
    {
    }
    public static class PrismEvents
    {
        public static readonly IEventAggregator EventAggregator = new EventAggregator();
        public static readonly TestEvent EventTest = EventAggregator.GetEvent<TestEvent>();
    }

I have a subscriber class where this event is subscribed using a lambda.Note the usage of a local variable(i) inside the subscription code

public class SubScriber
    {
        public SubScriber()
        {
            int i = 5;
            PrismEvents.EventTest.Subscribe(() =>
            {
                Console.WriteLine("Event Fired");//not getting called
                i = 10; //commenting this line will execute the subscription code
            });
        }

    }

In the publisher side Subscriber is created, then GC is called then event publised.

Subscription code is not getting executed!

 class Program
    {
        static void Main(string[] args)
        {
            new SubScriber();
            GC.Collect(); //commenting this line will execute the subscription code
            PrismEvents.EventTest.Publish();
            Console.ReadKey();
        }

    }

Couple of points

  1. Commenting the usage of local variable(i=10) will fix the issue.Subscription code will execute as expected

  2. Commenting GC.collect will fix the issue. Subscription code will execute as expected

What is the reason for this behavior?

Upvotes: 2

Views: 215

Answers (1)

Sybaris
Sybaris

Reputation: 91

Good questions. I don't have all the answers, but Prism uses WeakReference. Subscribe create a WeakReference on the delegate (action) use in argument. To be precise, the WeakReference is made on the Target of the delegate. Here some code to understand better what happens :

    public class SubScriber
    {
        public SubScriber()
        {
            int i = 5;
            Action action1 = () =>
            {
                Console.WriteLine("Event Fired action1");//not getting called
                i = 11; //commenting this line will execute the subscription code
            };

            Action action2 = () =>
            {
                Console.WriteLine("Event Fired action2");//will be called
            };

            Console.WriteLine("Target 1 = "+ action1.Target);
            Console.WriteLine("Target 2 = " + action2.Target);
            PrismEvents.EventTest.Subscribe(action1);
            PrismEvents.EventTest.Subscribe(action2);
        }

        ~SubScriber()
        {
            Console.WriteLine("SubScriber destructed");
        }
    }

    static void Main(string[] args)
    {
        new SubScriber();
        GC.Collect(); //commenting this line will execute the subscription code
        GC.WaitForPendingFinalizers(); // or Thread.Sleep(2000);
        Console.WriteLine("Publish");
        PrismEvents.EventTest.Publish();
        Console.WriteLine("Press a key to finish");
        Console.ReadKey();
    }

We saw that the "SubScriber destructed" is displayed before "Publish". What is interesting is also to use ILSpy to see what is generated :

public class SubScriber
    {
        [CompilerGenerated]
        private sealed class <>c__DisplayClass0_0
        {
            public int i;

            internal void ctor>b__0()
            {
                Console.WriteLine("Event Fired action1");
                this.i = 11;
            }
        }

        [CompilerGenerated]
        [Serializable]
        private sealed class <>c
        {
            public static readonly Program.SubScriber.<>c <>9 = new Program.SubScriber.<>c();

            public static Action <>9__0_1;

            internal void ctor>b__0_1()
            {
                Console.WriteLine("Event Fired action2");
            }
        }

        public SubScriber()
        {
            Program.SubScriber.<>c__DisplayClass0_0 <>c__DisplayClass0_ = new Program.SubScriber.<>c__DisplayClass0_0();
            <>c__DisplayClass0_.i = 5;
            Action action = new Action(<>c__DisplayClass0_.<.ctor>b__0);
            Action arg_41_0;
            if ((arg_41_0 = Program.SubScriber.<>c.<>9__0_1) == null)
            {
                arg_41_0 = (Program.SubScriber.<>c.<>9__0_1 = new Action(Program.SubScriber.<>c.<>9.<.ctor>b__0_1));
            }
            Action action2 = arg_41_0;
            string arg_59_0 = "Target 1 = ";
            object expr_4D = action.Target;
            Console.WriteLine(arg_59_0 + ((expr_4D != null) ? expr_4D.ToString() : null));
            string arg_7B_0 = "Target 2 = ";
            object expr_6F = action2.Target;
            Console.WriteLine(arg_7B_0 + ((expr_6F != null) ? expr_6F.ToString() : null));
            Program.PrismEvents.EventTest.Subscribe(action);
            Program.PrismEvents.EventTest.Subscribe(action2);
        }

        ~SubScriber()
        {
            Console.WriteLine("SubScriber destructed");
        }
    }

The difference I saw is that the 2nd action has a static readonly field that hold an instance on the delegate...

Regards, Sybaris

Upvotes: 1

Related Questions