2
\$\begingroup\$

I can't understand why this strange behavior when I call the method remotely is expensive.

I have observed a performance issue when my object method called remotely compared to when it's called within the same process. To confess, I don't have good understanding of .NET remoting technology. Yes I have read few articles, but I haven't understood how to resolve this specific problem.

I had supportive data from ants performance profiler which clearly shows call to DisconnectFromPublisher()-> UnsubscribeAll(); is expensive.

I have modified my code to simplify the problem. The skeleton is as follows. Give me some pointers why the method is expensive when it's invoked remotely.

/// <summary>
/// A data object that holds callback information.
/// </summary>
public class CallbackDelegate
{
    /// <summary>
    /// The delegate that should be invoked to perform the callback.
    /// </summary>
    public SampleDelegate SampleDelegate;

    /// <summary>
    /// The client control that has the callback method.
    /// This value can be null.
    /// </summary>
    public Control ClientControl;

    /// <summary>
    /// a unique key is used to identify each callback info
    /// </summary>
    public Guid CallbackDelegateKey;
}

/// <summary>
/// A helper class that allows clients to receive notification messages
/// by subscribing to topics.
/// </summary>
public class Subscriber : MarshalByRefObject
{
    #if DEBUG
    // Allows exceptions to be injected during unit testing of debug builds.
    private string _testCase = null;
    #endif

    /// <summary>
    /// The publisher to which this subscriber is connected.
    /// </summary>
    public Publisher Publisher
    {
        get { return _publisher; }
    }
    private Publisher _publisher;


    /// <summary>
    /// Notification Dispatcher
    /// </summary>
    private Dispatcher _Dispatcher = new Dispatcher();

    private Dictionary<string, List<CallbackDelegate>> _clientCallbacks = new Dictionary<string, List<CallbackDelegate>>();

    /// <summary>
    /// sync object for client callbacks list
    /// </summary>
    private object _syncObj = new object();
    /// <summary>
    /// Constructor for a notification subscriber.
    /// </summary>
    public Subscriber()
    {
        _Dispatcher.Notify += new EventHandler<NotificationEventArgs>                                   (_Dispatcher_Notify);
    }

    public void Unsubscribe(string topicName)
    {
        // Check the method parameter.
        if (topicName == null)
        {
            throw new ArgumentNullException();
        }
        try
        {
            lock (_syncObj)
            {
                // Remove the client callback (indexed by topic name).
                _clientCallbacks.Remove(topicName);
            }

        }
        catch (Exception)
        {
            throw;
        }
    }


    /// <summary>
    /// Unsubscribes from all topics that this object has susbcribed to.
    /// </summary>
    /// <exception cref="PublisherAccessException">An error occurred while         unsubscribing from a topic.</exception>
    public void UnsubscribeAll()
    {
        lock (_syncObj)
        {
            // Access the topic names before modifying the list (with Unsubscribe).
            Queue<string> topics = new Queue<string>();
            foreach (string name in _clientCallbacks.Keys)
            {
                topics.Enqueue(name);
            }

            //For each topic name...
            while (topics.Count > 0)
            {
                string topicName = topics.Dequeue();

                // Unsubscribe the topic.
                Unsubscribe(topicName);
            }
        }
    }

    /// <summary>
    /// Disconnects from the publisher.
    /// This subscriber will no longer subscribe to any topics.
    /// </summary>
    /// <exception cref="PublisherAccessException">An error occurred while unsubscribing from a topic.</exception>
    public void DisconnectFromPublisher()
    {
        try
        {
            _Dispatcher.Stop();
            // Unsubscribe from all topic.
            UnsubscribeAll();
        }
        catch (PublisherAccessException)
        {
            throw;
        }
        catch (Exception ex)
        {
            throw new PublisherAccessException("An error occurred while disconnecting from the notification publisher.", ex);
        }
        finally
        {
            // Do not keep a reference to the publisher.
            _publisher = null;
            lock (_syncObj)
            {
                _clientCallbacks.Clear();
            }
        }
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ Both Unsubscribe and UnsubscribeAll acquire a lock on _syncObj so I would guess that is the source. To be honest I'm surprised this doesn't cause a deadlock in the first place since the calls to Unsubscribe are nested inside UnsubscribeAll. \$\endgroup\$ – Jeroen Vannevel Oct 13 '14 at 9:31
  • \$\begingroup\$ @JeroenVannevel lock is recursive, so if the current thread already owns the lock, it can acquire it again. \$\endgroup\$ – svick Oct 13 '14 at 14:00
  • \$\begingroup\$ I'm not sure there's enough code here to figure out the performance problem. Could you include the code that uses this? \$\endgroup\$ – svick Oct 13 '14 at 14:01
1
\$\begingroup\$

First of all, Chapter 11 - Improving Remoting Performance looks promising. If not, maybe remoting performance and inner workings of MarshalByRefObject will help.

Next, a few observations/questions/ideas:

  1. Why iterate through and Unsubscribe for each topic? Just clear the dictionary and be done with it.
  2. You can initialize a queue with an IEnumerable, so don't iterate to enqueue items (especially because your code doesn't care in what order things are done).
  3. Throwing exceptions is very expensive - is that happening in the remote case? In fact, Remove should only throw if the argument is null, and you're already checking for that, so try just removing the try/catch.
  4. As mentioned before, your code doesn't care about the order of operations, which means that it is a perfect candidate for parallel processing. Why not use a ConcurrentDictionary or similar data structure and do the operations in parallel?
  5. Why are you using MarshalByRef? Do you know how expensive is MarshalByRefObject compared to serialization?

None of these specifically address why the remote call is slow, but you should be able to try them and see if perf improves.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.