1

I've made a little library called SignalR.EventAggregatorProxy

Before I push it into 1.0 state I need to fix so it works safely with concurrent operations. Easiest way is lock all operations but thats a huge performance impact.

The library queues event subscriptions and when a event comes in it checks the subscriptions and updates the clients using SignalR

This is the class that holds the subscriptions

There are 3 methods that write/read to the subscription collection(s) (I aggregate the subscription both on a client level and event level, so its two collections)

  • Subscribe
  • UnsubscribeConnection
  • Unsubscribe

And one that that reads

  • Handle

I realize since this is a library I can't make it optimum for all users of the library, but how do I find a good middle way that does not use locks?

I think the Handle method is the most important method and should be prioritized for performance over the other 3.

I made this little Unittest to test for Concurrency fail

update: I choose to have locked writes and unlocked reads. The writes didnt mutate existing state but overwrite the collection completely.

1
  • What you seem to need is prioritized queue with probably special attention to reading methods which may or may not be locked. And also you cannot handle concurrent usage of shared resources without synchronization which eventually leads to locks.
    – superM
    Commented Aug 12, 2013 at 8:27

2 Answers 2

1

You can't get away from using locks. You must ensure that calls to the Subscribe method will always atomically set the internal state with the new subscriber - so you must assume that someone will call that subscribe method in 2 threads at exactly the same time. Last thing you want is for 1 thread to succeed and add the new subscriber, only for the 2nd thread to jump in and set its subscriber only to stomp over the 1st subscriber's entry in the internal collection. Finding that bug would be a nightmare as the user report will be "I subscribed and nothing happened" or worse "I subscribed, it returned success but I received no subscriptions".

It is possible the Handle method doesn't need to lock - as long as you can be sure that someone removing or adding a new subscriber doesn't trash the state of the internal collection (ie a naive implementation would loop over all subscriptions... but what if the next loop comes across an entry that has just been removed? The answer is usually a crash, but sometimes undefined, even in a GC system where the removed object isn't collected yet, it will be one day, usually just when its being used in a demo and then you'll get the crash :-) ).

So to avoid locks.. you could make a copy of the internal collection of subscribers and use it in the Handle method without locking, so you have 2 collections - one of all subscribers, and one of 'active' subs. You can then block the Handle method and swap them when new members are added or removed in the Handle method as you'll be the only one calling that method to send out to the subscribers.

Or you can extend this to add new subscribers to a different collection and safely merge them in the Handle method, but you will still have to cater for the case where 2 new subs are added simultaneously.

4
  • Thanks for input, I tried only to lock the 3 subscribe methods and copy the subscription collection (.ToList()) before iterating it in the Handle function, it did lower the probablity of a crash. But it still happened in my unittest. I put a lock around the copy function and it works. But then I cant scale the code to more Threads. I will try Concurrent collections and see how they perform. Its hard to benchmark Multithreaded code because the diagnostic must be thread safe and that has impact on the benchmark :D
    – Anders
    Commented Aug 13, 2013 at 8:02
  • its easy to benchmark MT code. What's difficult is diagnosing problems whilst benchmarking - as you're finding. Yes, you need to protect the copy if you're using my suggestion - rule of thumb, lock all shared data every time it gets accessed. 2nd rule of thumb, hold the lock for as short a time as possible.
    – gbjbaanb
    Commented Oct 11, 2013 at 11:54
  • Well, I went with only lock it when you write, but not read. And I do not mutate state of data that is accessible from unlocked methods. Good middle way. Scales a little better than lock all at least
    – Anders
    Commented Oct 11, 2013 at 12:08
  • I could maybe make it a little fast by using a const-list (Immutable linked list) because the IList<> performs pretty bad
    – Anders
    Commented Oct 11, 2013 at 12:33
0

Don't.

It's not a static class, and they aren't static methods, so there's no reason why it shouldn't be up to calling code to avoid concurrent calls.

With static methods, then you would have to do some of the work to make it handle concurrent calls because calling code can't guarantee that it is the only calling code.

And of course with instance methods specifically geared at synchronising data-access or otherwise where being hit simultaneously by multiple threads is part of the point of it, then of course you have to be able to have that concurrent use.

But otherwise, you're just in the way. You're in the way of the user who knows they only ever hit the methods one at a time and so don't need any locking. You're in the way of the user who knows they hit the methods with several threads and therefore have coded a concurrency-handling strategy suitable to their use. And at the time of writing you're really in the way of someone who locks on the EventProxy object itself because you're using lock(this) so you're locking on an object the writer of the calling code should be able to think of as "theirs".

2
  • Valid points, but in this case there is no "user code" between the entry point (SignalR Hub) and my code. Btw, even Microsoft have thread safe classes that are not static classes
    – Anders
    Commented Apr 1, 2015 at 20:52
  • Oh, does the use mean the instance gets shared by SignalR Hub? (I don't know SignalR at all). That would make it fall into the case in my second last paragraph above. In that case I'd still recommend not locking on this though, just as general good practice.
    – Jon Hanna
    Commented Apr 1, 2015 at 22:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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