0

I created a duplex service (NetTcpBinding) like this example: I used the publish-subscribe pattern, and for each connection request from a new client, it creates a new instance of the service (containing a different callback). In this example, the callback is invoked through events and delegates.

Now I would like to change this example: suppose we do not want to respond immediately to client's request, that is, suppose we want to invoke the callback method after a certain time interval. In this case I need to maintain a reference to the method of the callback... But what happens if in the meantime some client disconnects? The service instance is destroyed and we lose even the callback...

I wrote this example:

  • MySingletonTable is the data structure that stores the references to the methods of the callbacks;
  • SampleService is not a service, but simulates the instance of a service;

    public delegate void ProcessingHandler(string item, double price, double change);
    
    public class MySingletonTable
    {
        private static volatile MySingletonTable m_Instance;
        private static object syncRoot = new object();
    
        private static Dictionary<string, ProcessingHandler> pointersToHandlers;
    
        private MySingletonTable()
        {
            pointersToHandlers = new Dictionary<string, ProcessingHandler>();
        }
    
        // Return the singleton instance of this class.
        public static MySingletonTable Instance
        {
            get
            {
                if (m_Instance == null)
                {
                    lock (syncRoot)
                    {
                        if (m_Instance == null)
                            m_Instance = new MySingletonTable();
                    }
                }
                return m_Instance;
            }
        }
    
        /// The number of the entries in the table.
        public int Count
        {
            get
            {
                lock (syncRoot)
                {
                    return pointersToHandlers.Count;
                }
            }
        }
    
        // Add an handler.
        public void Add(string id, ProcessingHandler handler)
        {
            lock (syncRoot)
            {
                if (!pointersToHandlers.ContainsKey(id))
                    pointersToHandlers.Add(id, handler);
            }
        }
    
        // Get an handler from the table.
        public ProcessingHandler GetHandler(string id)
        {
            ProcessingHandler handler = null;
            lock (syncRoot)
            {
                if (pointersToHandlers.ContainsKey(id))
                    handler = pointersToHandlers[id];
            }
            return handler;
        }
    
        // Remove the specified handler.
        public bool Remove(string id)
        {
            lock (syncRoot)
            {
                return pointersToHandlers.Remove(id);
            }
        }
    }
    
    // This class simulates the service.
    public class SampleService
    {
        private static int counter = 0;
        private int service_i = ++counter;
    
        MySingletonTable reference = MySingletonTable.Instance;
    
        public SampleService(string id)
        {
            reference.Add(id, PriceChange);
        }
    
        private void PriceChange(string item, double price, double change)
        {
            // call the callback
            // ...
            Console.WriteLine("service_i {0}: {1} {2} {3}", service_i, item, price, change);
        }
    }
    
    
    class Program
    {
        static void Main(string[] args)
        {
            SampleService s1 = new SampleService("abc");
            SampleService s2 = new SampleService("def");
    
            MySingletonTable table = MySingletonTable.Instance;
            ProcessingHandler handler = null;
    
            handler = table.GetHandler("abc");
            handler("item one", 10, 20);
    
            handler = table.GetHandler("def");
            handler("item two", 30, 40);
    
            Console.ReadLine();
        }
    }
    

Obviously I can not explicitly destroy the two service instances simulated in this example. What would happen, however, if s1 and s2 were two instances of a service related to two different clients?

enzom83
  • 8,080
  • 10
  • 68
  • 114
  • It looks weird to me. Delegate internally hold a linked list. Not sure why you are building your own List ( += is building an internal list of callBacks). – MBen Jun 25 '12 at 20:52
  • @MBen: I just wrote the full sample, so I can get a better understanding of my doubt. – enzom83 Jun 25 '12 at 21:49

3 Answers3

1

If your delegate is stored in pointersToHandlers, then this will be holding onto your object so it is never going to be garbage collected, or as you call it, 'destroyed'. Effectively, you also have a memory leak right now.

You will need to remove the delegate from the list when clients disconnect (or whenever you are expecting the object to be destroyed). Not sure why you are not using events, but that is another question. Even if you do use events, you will still need to disconnect once you are done so that you dont end up with this situation.

Blueberry
  • 2,211
  • 3
  • 19
  • 33
  • Ok, but suppose the communication between client and service is interrupted in an unexpected way: if the method is invoked before the service detects the lost of connection, what happens? – enzom83 Jun 25 '12 at 22:09
  • 1
    It will attempt to call the delegate, naturally, as the host has no idea that a client has disappeared. What you will need to do is handle the situation where the connection has been lost and do a clean up to make sure these objects are destructed. Just out of interest, are these callbacks to the client? If so, you could just detect this and call a 'Disconnect' method if there is an exception thrown (which there will be if there is no client left). – Blueberry Jun 25 '12 at 22:19
  • Good explanation here as to the differences between events, delegates and multicast delegates. http://blog.monstuff.com/archives/000040.html. What I am trying to say is theres really no need to keep a list of delegates as both multicast delegates and events do this for you. Its just unnecessary. – Blueberry Jun 25 '12 at 22:31
  • 1
    P.S. The fact that you changed the question will make this answer really confusing for other users looking at it in the future. :)... If you need more than just a list of delegates which you will call later on, what I have said above may not count. Looks like you have already removed the items from the dictionary on disconnection too. All you now need is to remove them as part of exception handling too and you are done. – Blueberry Jun 25 '12 at 22:41
  • Ok, but I think that since an event can only be invoked from within the class that declared it, I could not invoke it from other threads: what I need is the ability to invoke the callback from a different thread, because before replying to the client, I need to process the received data in another thread. – enzom83 Jun 25 '12 at 22:42
  • Again, that was an answer to your question before the rewrite. Looks like Events wont suit you here. Hope that helps! – Blueberry Jun 25 '12 at 22:45
  • I changed the name of `m_Table` making it `pointersToHandlers`. – enzom83 Jun 25 '12 at 22:53
1

Ok, well you are setting a local variable S1 and S2 to null. However the Dictionary still has the address to the object. The SampleService created by the new operator are not deleted when you do s1 = null, that's why calling handler after is still working. This is the same thing as doing :

var s1 = new SampleService("aa");
var handler = s1;
s1 = null;

handler is still referencing the created Object.

MBen
  • 3,956
  • 21
  • 25
  • I realized my error, so I can not simulate the destruction of the service instance. I suppose that the two instances are not destroyed because there are two delegates who refer to them. – enzom83 Jun 25 '12 at 22:12
  • 1
    What I would, is to detect when cliens get disconnected if you can(I don't know what are you using) and would remove the callBack. – MBen Jun 25 '12 at 22:15
  • I would also use Action instead of delegate. – MBen Jun 25 '12 at 22:17
  • What are the advantages of `Action`? – enzom83 Jun 25 '12 at 22:19
  • Well Action is a delegate :-) it is just a nice syntactic sugar. Plus when I see a raw delegate I am tempted to think about the += operator. But in his he really just want a method to call sometime in the futur. – MBen Jun 25 '12 at 22:22
  • If I understand it, the instance of a service should not be destroyed by the garbage collector until you end the session: therefore, if the session is terminated properly, I should remove the callback from the delegate. If the connection is lost and the callback is invoked before the service detects the disconnection of the client, an exception should be thrown (I just read [this link](http://stackoverflow.com/questions/1427926/detecting-client-death-in-wcf-duplex-contracts)), so I can handle it and then delete the callback from the table. Is it correct? – enzom83 Jun 25 '12 at 22:26
  • Out of idle curiosity: what happens if, at the time of disconnection (whether voluntary or unexpected), I did not remove the callback from the table? – enzom83 Jun 25 '12 at 22:33
0

How about...

if (kDelegate.Target != null) kDelegate();
Jason King
  • 649
  • 7
  • 13
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. – Erwin Bolwidt Apr 11 '14 at 01:42
  • Actually it does answer enzom83's question "How to handle a delegate previously associated with a method of an object that now does no longer exist?". If the delegate to be used has a target equal to null, then that target no longer exists and the delegate should not be called. – Jason King Apr 11 '14 at 17:50