Elemar DEV

Negócios, tecnologia e desenvolvimento

Weak Event Patterns – Parte 1 – Event Wrapper

Olá pessoal. Tudo certo?

Eventos são uma abstração extremamente inteligente e poderosa. A sintaxe oferecida pelo C# para suporte de eventos é bela e simples de entender. Entretanto, tem algumas armadilhas importantes.

Considere o código que segue:

using System;

namespace EventLeakDemo
{
    internal class Program
    {
        private static void Main()
        {
            Console.Title = "Walking dead objects";
            var s = new Source();
            Foo(s);

            //
            GC.Collect(0);
            //

            s.RaiseMyEvent();
        }

        static void Foo(Source s)
        {
            var c = new Consumer(s, "dead object");
        }
    }

    internal class Source
    {
        public event EventHandler MyEvent;

        public void RaiseMyEvent()
        {
            EventHandler eh;
            lock (this) { eh = MyEvent; }
            if (eh != null) eh(this, EventArgs.Empty);
        }
    }

    internal class Consumer
    {
        private readonly string _id;

        public Consumer(Source s, string id)
        {
            _id = id;
            s.MyEvent += OnMyEvent;
        }

        private void OnMyEvent(object sender, EventArgs e)
        {
            Console.WriteLine("Event was listened by '{0}'", _id);
        }
    }
}

Nesse breve exemplo, temos uma classe que “gera” eventos e outra que os consome. Na inicialização, criei uma instância da classe que gera e, depois, criei em um escopo delimitado uma instância da classe que consome.

Em primeira vista, o evento disparado pela “Source” jamais seria escutado por “Consumer”, certo? Afinal, a instância de Customer deveria morrer e ser coletada antes do evento ser disparado, concorda? Errado! Quando assinamos um evento, criamos uma referência direta de quem está “disparando eventos” para quem está “escutando”. Logo, a instância de “Customer” não é coletada pois ainda possui uma referência (em “Source”).

Veja:

weakevent1

Uma solução

A solução direta para o problema acima é cancelar a assinatura dos eventos de “customer” antes desse objeto ser descartado. Entretanto, esse tipo de cuidado é extremamente passível de falhas.

Abaixo uma solução mais “elaborada” e “a prova de enganos”.

using System;

namespace WeakEvent.SimpleEventWrapper
{
    internal class Program
    {
        private static void Main()
        {
            Console.Title = "Walking dead objects";
            var s = new Source();
            Foo(s);

            // 
            System.GC.Collect(0);
            //

            s.RaiseMyEvent();
        }

        static void Foo(Source s)
        {
            var c = new Consumer(s, "dead object");
        }
    }

    internal class Source
    {
        public event EventHandler MyEvent;

        public void RaiseMyEvent()
        {
            EventHandler eh;
            lock (this) { eh = MyEvent; }
            if (eh != null) eh(this, EventArgs.Empty);
        }
    }

    internal class Consumer
    {
        private readonly string _id;
        private MyEventWrapper _myEventWrapper;
        public Consumer(Source s, string id)
        {
            _id = id;
            //s.MyEvent += OnMyEvent;
            _myEventWrapper = new MyEventWrapper(s, this);
        }

        public void OnMyEvent(object sender, EventArgs e)
        {
            Console.WriteLine("Event was listened by '{0}'", _id);
        }
    }

    internal class MyEventWrapper
    {
        private readonly WeakReference<Consumer> _wr;
        private readonly Source _source;

        public MyEventWrapper(
            Source source,
            Consumer consumer
            )
        {
            _wr = new WeakReference<Consumer>(consumer);
            _source = source;
            source.MyEvent += OnMyEvent;
        }

        public void OnMyEvent(
            object source,
            EventArgs e
            )
        {
            Consumer c;
            if (_wr.TryGetTarget(out c))
            {
                c.OnMyEvent(source, e);
            }
            else
            {
                Deregister();
            }
        }

        public void Deregister()
        {
            _source.MyEvent -= OnMyEvent;
        }
    }
}

Executando …

weakevent2

A ideia é deixar de assinar os eventos diretamente criando um “wrapper” que mantém uma “referência fraca” para “Consumer”. Assim, a coleta poderá ocorrer naturalmente. Pegou?

Esse conceito fundamental está no “core” do WPF. Mais adiante, voltarei a abordar essa estratégia mostrando abordagens mais elegantes (e menos trabalhosas).

Era isso.

Deixe um comentário

Informação

Publicado às 25/04/2014 por em Post e marcado , .

Estatísticas

  • 921.488 hits