почему сборка мусора, закрытие и лямбда отключают подписку на агрегатор событий prism?

У меня есть простое событие, определенное с использованием шаблона агрегатора событий PRISM.

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

У меня есть класс подписчика, в котором это событие подписывается с помощью лямбда. Обратите внимание на использование локальной переменной (i) внутри кода подписки.

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

    }

На стороне издателя создается подписчик, затем вызывается GC, а затем публикуется событие.

Код подписки не выполняется!

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

    }

Пара очков

  1. Комментирование использования локальной переменной (i=10) устранит проблему. Код подписки будет выполняться, как и ожидалось.

  2. Комментирование GC.collect решит проблему. Код подписки будет выполняться, как ожидалось

В чем причина такого поведения?


person Jimmy    schedule 07.03.2019    source источник
comment
Я не знаю, как C# это делает, но, учитывая, как Java обрабатывает захватывающие и не захватывающие лямбда-выражения, экземпляр SubScriber совершенно не имеет значения, но незахватывающее лямбда-выражение можно использовать повторно, следовательно, связать с создающим его кодом, который будет использовать один и тот же объект каждый раз, когда вы его выполняете, что предотвратит его сборку мусора. Напротив, лямбда-выражение, фиксирующее текущую переменную i, должно создавать новый экземпляр каждый раз при выполнении кода. Следовательно, код не будет хранить ссылку на него, и его можно будет собрать сразу после этого.   -  person Holger    schedule 07.03.2019


Ответы (1)


Хорошие вопросы. У меня нет всех ответов, но Prism использует WeakReference. Подпишитесь, создайте WeakReference для использования делегата (действия) в качестве аргумента. Чтобы быть точным, WeakReference создается на Target делегата. Вот некоторый код, чтобы лучше понять, что происходит:

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

Мы видели, что «Подписчик уничтожен» отображается перед «Опубликовать». Интересно также использовать ILSpy, чтобы увидеть, что сгенерировано:

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

Разница, которую я увидел, заключается в том, что второе действие имеет статическое поле только для чтения, которое содержит экземпляр делегата...

С уважением, Сибарис.

person Sybaris    schedule 15.01.2020