Megosztás a következőn keresztül:


What's wrong with this code - #6

After a long hiatus, here's the next entry in my not-really-regular-enough-to-honestly-be-called-periodic-(though-I-try-to-keep-up-a-somewhat-reasonable-schedule-I-don't-seem-to-keep-it-since-I'm-lazy-are-you-still-reading-this)-series about C# code.

Anyway, here's some code that I've seen a fair bit, and written myself:

 class Notifier

{

   ArrayList items = new ArrayList();

   public void Add(object o) {

      lock (this) {

         items.Add(o);

      }

   }

}

 

This code will work fine, but it has a latent issue. What is the issue, and how should it be addressed?

Comments

  • Anonymous
    April 22, 2005
    You shouldn't be locking on this, rather, you should be locking on the object exposed by the SyncRoot property on the ArrayList.
  • Anonymous
    April 22, 2005
    Would it be better to just lock the items object, since that is all that is used in the lock statement?
  • Anonymous
    April 22, 2005
    The only thing that I can think of - (apart from the bracketing style :))

    Is that we are locking an object (this) that is external - a caller could have locked it in which case we have a deadlock.

    I'd suggest
    lock(items)
    items.Add(o);
  • Anonymous
    April 22, 2005
    If you lock on this then someone else can lock on you, and that's pretty bad...
    You should lock on a new private object in your class or in the SinkRoot of the ArrayList...
  • Anonymous
    April 22, 2005
    Calling lock(this) can cause deadlocks. It would be recommended to have the Notifier class hold a dummy object that it can lock on or lock specifically on the object you are sharing across threads.
  • Anonymous
    April 22, 2005
    I am guessing the problem is the locking itself, by locking "this" you are locking the entire class and not just the collection.

    When you do answer this can you explain why you would even want to lock the array list. I know it is to make it thread safe so multiple threads can't change it, but wouldn't it be better to just use the syncronized method? I guess I always thought of an ArrayList as a collection with a Changed event handler. This would invalidate any enumerations if something changed while another thread was looping through it. However upon looking at the array list further I see it doesn't have a public handler. Does it have a private one? I am thinking it should be like any other collection where if one thread changes it while another is looping it will throw. But I am not sure on the ArrayList I do not use it that much. Hmm off to ILDASM the ArrayList.
  • Anonymous
    April 22, 2005
    The comment has been removed
  • Anonymous
    April 22, 2005
    Definitely locking on "this" opens up the potential for a deadlock, but I wouldn't lock on SyncRoot either since it's a public property and thus could suffer from the same latent bug that locking on "this" would have.

    Ideally you should lock on a private variable. In this case, items seems the logical choice.
  • Anonymous
    April 22, 2005
    The comment has been removed
  • Anonymous
    April 22, 2005
    The comment has been removed
  • Anonymous
    April 22, 2005
    My instinct says that the latent issue is caused by the fact that the instance methods of an ArrayList aren't thread safe. Sure, you've syncrhonized the Add, but enumerations and removes aren't protected.

    If I'm right, the solution would be to use the Synchronized version of the ArrayList. So the first declaration would be changed to ArrayList items = ArrayList.Synchronized(new ArrayList());
  • Anonymous
    April 22, 2005
    As noted in the .NET library design guidlines[1], you shouldn't be locking at all in instance methods of a library. The application layer is the only place where the true needs of thread interaction are ever really known. Therefore building your libraries to deal with synchronization is a sure fire way to guarentee they will never perform well for applications who are only ever using the library in a safe, single threaded fashion.

    [1] http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconthreadingdesignguidelines.asp
  • Anonymous
    April 22, 2005
    Notifier lockMe = new Notifier();
    lock(lockMe)
    {
    lockMe.Add(new object());
    }

    Your internal locking object should not be accessable outside of your class, using the this reference makes it public to anyone. hilarity does not ensue. :|
  • Anonymous
    April 22, 2005
    I don't think lock(this) is bad thing here, though it is not good practice in general. Notifier is internal class and doesn't implement any interfaces, thus it's unlikely, that it will be published to user. It may be returned as object, but this would be strange. Though, I would fix this as well.

    Another issue besides locking is that Notifier.Add doesn't return index of added element, so if user will need it, it will have to use IndexOf and slow down performance. So, it should have been rewrite as

    public int Add(object o)
    {
    lock(items) { return items.Add(o); }
    }

    One more thing I can think of is type-safety. I think that Add should be typed, but this depends on the context.
  • Anonymous
    April 22, 2005
    The comment has been removed
  • Anonymous
    April 22, 2005
    The comment has been removed
  • Anonymous
    April 22, 2005
    items is a public object and you're trying to lock down the Notifier object so that only one thing can be added at a time. However, because items is public and not private you can't guaruntee that.

    Or this could be the reason why it's wrong:

    The expression of a lock statement must denote a value of a reference-type. No implicit boxing conversion (Section 6.1.5) is ever performed for the expression of a lock statement, and thus it is a compile-time error for the expression to denote a value of a value-type.

    From the help search
  • Anonymous
    April 24, 2005
    orangy,

    Thanks for your comment. I've never thinked about this.
    Exposing internal data must be done carefully.

    Secure coding is not easy and very often requere code to be a little bit complicated.

    BTW, Exposing any object for locking will be unsafe - as some evil threads can do Monitor.TryEnter() on everything it see ;-)

    Regarding your comments - It's very sad that there is no ArrayList.Synhronized(ArrayList array, object lockObject) methods. (Same problem in Java - two arguments contructor for Collection taking lock object is package private).

    You have to create own wrapper to make custom synhronization.

    With your suggestion last code can (but it unable to) look like

    class Notifier
    {
    public object SyncRoot { get { return items.SyncRoot; }}

    readonly IList items = ArrayList.Synchronized(new ArrayList(), new object()); // Sync arraylist using supplied object as lock gate

    public void Add(object o) {
    items.Add(o);
    }
    }
  • Anonymous
    April 24, 2005
    We should declare a static object variable, and lock the static object variable, not the this reference.
  • Anonymous
    April 24, 2005
    class Notifier

    {
    ArrayList items = new ArrayList();
    public void Add(object o) {
    lock (items.SyncRoot) {
    items.Add(o);
    }
    }
    }
  • Anonymous
    April 27, 2005
    The comment has been removed