views:

157

answers:

2

Let's say I want to create a collection class that is thread-safe by default.

Internally, the class has a protected List<T> property called Values.

For starters, it makes sense to have the class implement ICollection<T>. Some of this interface's members are quite easy to implement; for example, Count returns this.Values.Count.

But implementing ICollection<T> requires me to implement IEnumerable<T> as well as IEnumerable (non-generic), which is a bit tricky for a thread-safe collection.

Of course, I could always throw a NotSupportedException on IEnumerable<T>.GetEnumerator and IEnumerable.GetEnumerator, but that feels like a cop-out to me.

I already have a thread-safe getValues function which locks on Values and returns a copy in the form of a T[] array. So my idea was to implement GetEnumerator by returning this.getValues().GetEnumerator() so that the following code would actually be thread-safe:

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

foreach (T value in coll) {
    // do something with value
}

Unfortunately this implementation only seems to work for IEnumerable.GetEnumerator, not the generic version (and so the above code throws an InvalidCastException).

One idea I had, which seemed to work, was to cast the T[] return value from getValues to an IEnumerable<T> before calling GetEnumerator on it. An alternative would be to change getValues to return an IEnumerable<T> in the first place, but then for the non-generic IEnumerable.GetEnumerator simply cast the return value from getValues to a non-generic IEnumerable. But I can't really decide if these approaches feel sloppy or perfectly acceptable.

In any case, does anyone have a better idea of how to go about doing this? I have heard of the .Synchronized methods, but they seem to only be available for non-generic collections in the System.Collections namespace. Maybe there is a generic variant of this that already exists in .NET that I simply don't know about?

+1  A: 

Unfortunately this implementation only seems to work for IEnumerable.GetEnumerator, not the generic version (and so the above code throws an InvalidCastException).

Seems strange for me. Have you implemented non-generic IEnumerable explicitly? I.e. have you written

public IEnumerator<T> GetEnumerator() { ...}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator<T>(); }

Also, have you tried to implement IEnumerable with iterators syntax. It should be easy:

public IEnumerator<T> GetEnumerator()
{
    T[] values;
    lock(this.Values)
        values = this.Values.ToArray();
    foreach(var value in values)
        yield return value;
}

If you've tried, why doesn't it suits your needs?

elder_george
So it seems you are advocating a slight variation on the second proposal I made -- casting the result of `IEnumerable<T>.GetEnumerator` to an `IEnumerable` for `IEnumerable.GetEnumerator`. I will certainly try that. Otherwise, I think JS Bangs made the excellent point that copying the values to an array shouldn't even be necessary as long as I've locked on the internal collection property.
Dan Tao
Yes, casting here seems simple to me, since generated IEnumerator<T> implementation will be IEnumerator as well. IIRC, compiler won't even require explicit cast here.On copying vs. locking: it depends on your collection usage. If collection would be small (<100000 elements I think), copying would reduce lock contention during cross-thread reading. If it's expected to be larger, it would be better to lock it. YMMV, of course.If you'll decide to lock collection, consider using ReaderWriterLockSlim instead of lock{}, since it should reduce lock contention a bit.
elder_george
+3  A: 

Most collections specify that you cannot add or remove things from the collection while iterating. It follows that for a thread-safe collection, you want to lock out other threads from modifying the collection while any thread is iterating. This should be easy to do with the iterators syntax, and doesn't require you to make a copy:

public IEnumerator<T> GetEnumerator()
{
    lock (this.Values) // or an internal mutex you use for synchronization
    {
        foreach (T val in this.Values)
        {
            yield return val;
        }
    }
    yield break;
}

This assumes that all of the other operations that might modify the collection also lock this.Values. As long as that's true, you should be fine.

JSBangs
I must admit to not being particularly aware of the `yield` keyword prior to these answers... I'd seen it, but never really understood its purpose. This was quite helpful; thanks!
Dan Tao
This is apparently problematic, in that the lock can stick around if the enumerator isn't taken to its conclusion immediately. See Jon Skeet's last reply here: http://www.eggheadcafe.com/software/aspnet/33035743/c-20-iterators-and-lock.aspx
AaronSieb
I think that the behavior of the lock here is exactly what's desired: the lock will not be released until either the iteration finishes or the enumerator is disposed. (In particular, you don't want the lock to be released between calls to MoveNext(), as that implies that the collection might change during iteration.) The app writer should be aware of this and program accordingly.
JSBangs
Fair enough. :)
AaronSieb