views:

359

answers:

3

I am a threading noob and I am trying to write a custom thread safe generic list class in C# (.NET 3.5 SP1). I've read Why are thread safe collections so hard?. After reviewing the requirements of the class I think I only need to safely add to the list and return the list. The example shows pretty much all I want except it lacks the return list method therefore I've written my own public method like below:

Update: based on suggestions given I've reviewed my requirements and therefore simplified the class to as below:

public sealed class ThreadSafeList<T>
{
    private readonly IList<T> list = new List<T>();
    private readonly object lockable = new object();

    public void Add(T t)
    {
        lock (lockable)
        {
            list.Add(t);
        }
    }

    public IList<T> GetSnapshot()
    {
        IList<T> result;
        lock (lockable)
        {
            result = new List<T>(list);
        }
        return result;
    }
}
+1  A: 

In my experience you have to use your brain when it comes to thread-safety and not rely on solutions such as these. in short, it depends on what the receiver of the list is going to be doing with it.

Orentet
Please elaborate on "not rely on solutions such as these". Thanks.
Jeffrey C
I believe he means solutions that handle thread sync internally. Your Add method returns the INTERNAL list, which is not synchronized, allowing the receiver of that list to do whatever the hell they want whenever they want, with or without locking. Even if they lock...it would be a different object they lock on, and synchronization is impossible at that point. You should leave thread sync up to the consumer(s), rather than try to handle it internally. Writing a coherent thread-save concurrent collection is no trivial task.
jrista
@jrista, actually this question is the follow up of my previous question http://stackoverflow.com/questions/2322557/ and I believe this ThreadSafeList class is actually being used in the consumer??? (the method that calls method 1 and method 2).
Jeffrey C
It doesn't matter where your ThreadSafeList is being used. The simple fact of the matter is, your Add method is completely flawed from a threading perspective. It returned unsynchronized internal state to the caller. If you want your ThreadSafeList to actually BE safe, you need to change what is returned. Either return nothing, or the index of the entry that was added. But do not return ANY internal state...otherwise your locking is useless.
jrista
@jrista, I thought what you are describing has been fixed.
Jeffrey C
Oh, sorry, I did not reread the OP. There are still several other flaws that I can see...such as accessing the IsReadOnly property within an existing lock context, which might cause deadlock (I can't remember off the top of my head how .NET treats nested lock contexts.) You are also not throwing ReadOnlyException if you try to add while the list is read-only, which for all intents and purposes would leave the list in an unknown state as far as the caller is concerned, leading up to an ArgumentOutOfRangeException if the caller tries to get the item that was not added, etc.
jrista
+1  A: 

The Translate() method looks correct. Using the lock you are preventing others from adding or otherwise modifying your list while you are in Translate/AddRange.

I think there might be a problem with your IsReadyOnly property though. You use a lock when reading/writing the property internally. But there also is a public getter which is not locked. It might happen that thread 1 calls MarkAsReadOnly while a second thread might still get false when looking at IsReadOnly. I'd use a normal property instead and either lock in the getter or use a volatile bool field.

stmax
@stmax, thanks and I've updated the code.
Jeffrey C
+1  A: 

Agree with @jrista. There's a semantics issue you need to resolve, and why is it called Translate()? What is the intent?

A - current code - return a read-only wrapper of the internal list

return new ReadOnlyCollection<T>(list);

You still have threading issues if the original list is changed if another thread is iterating over the list. As long as you're aware of this, it isn't a huge issue.

B - a read-only copy.

return new List<T>(list).AsReadOnly();

This list has no threading issues because nothing modifies the new list. The only reference held is by the ReadOnlyCollection<T> wrapper.

C - a normal (writable) copy

return new List<T>(list);

Returns a new list, and the caller can do what they wish to their list without affecting the original list, and changes to the original list do not affect this list.


Does it matter if another consumer grabs a copy of the list and then modifies their copy? Do consumers need to see changes to the list? Do you just need a thread-safe enumerator?

public IEnumerator<T> ThreadSafeEnumerator()
{
    List<T> copy;
    lock(lockable)
        copy = new List<T>(list);

    foreach (var value in copy)
        yield return value;
}
Robert Paulson
@Robert, the answers to your last few questions are no. Thanks for your insight explanations. I've updated the name of the method to GetSnapshot() and I believe now it better explains the intent.
Jeffrey C
@Jeffrey cool. GetSnapshot() is a much better name. Good job.
Robert Paulson