Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making GetEnumerator ThreadSafe

Tags:

c#

enumeration

Exactly how do enumerators work - I know that they build a state machine behind the scenes but if I call GetEnumerator twice will I get two different objects?

If I do something like this

  public IEnumerator<T> GetEnumerator()
  {
     yield return 1;
     yield return 2;
  }

Can I aquire a lock at the start of the method and is this lock held until the enumerator has returned null or until the enumerator is GC'd?

What happens if the caller resets the enumerator etc -

I guess my question is what is the best way to manage locking when dealing with an enumerator

Note: The client cannot be responsible for thread syncronisation - The class internally needs to be

And finally the example above is a simplification of the problem- The yield statements do a bit more then what I have shown :)

like image 670
Jack Kada Avatar asked May 08 '11 08:05

Jack Kada


People also ask

Is enumeration thread safe?

Enumerating through a collection is intrinsically not a thread-safe procedure. Even when a collection is synchronized, other threads could still modify the collection, which causes the enumerator to throw an exception.

What is GetEnumerator C#?

The C# GetEnumerator() method is used to convert string object into char enumerator. It returns instance of CharEnumerator. So, you can iterate string through loop.

Is foreach thread safe?

The foreach operation itself is not thread safe. Say if you run a foreach loop to remove item from forward direction, it'll fail with "Collection was modified; enumeration operation may not execute." exception message.


2 Answers

Yes, each call to your GetEnumerator() method will create a different object (a new state machine).

You can acquire a lock within an iterator block, but be aware that none of the code in your method will be called until the caller calls MoveNext() for the first time.

In general, I would advise against holding a lock within an iterator block if at all possible. You don't know what the caller is going to do between calls to MoveNext(). So long as they dispose of the iterator at some point, the lock will be released eventually, but it still means you're at the mercy of the caller.

If you can give us more information about what you're trying to do, that would help. An alternative design which might be easier to get right would be:

public void DoSomething(Action<T> action)
{
    lock (...)
    {
        // Call action on each element in here
    }
}
like image 80
Jon Skeet Avatar answered Oct 28 '22 13:10

Jon Skeet


As John already said it is difficult to give you a good answer. The obvious google search leads to: http://www.codeproject.com/KB/cs/safe_enumerable.aspx

The idea behind this is to lock the IEnumerable instance on construction which has major drawbacks.

The next obvious thing is isolation where you create a copy of your structure and iterate over the copy. This is if naively implemented very memory consuming but it can be worth it if your data set is relatively small.

The best thing would be if your data is immutable then you have automatic thread safety but if you are bound to a collection which count does change you have a mutable data structure. If you could redesign your data structure into an immutable one you are done.

Since it is not a good idea to lock the data for a potentially long time you can implement strategies to achieve thread safety when you take advantage of you exact data structure and use case. If you for example change the data rarely and enumerate it frequently you could implement an optimistic enumerable which does read before start of the enumeration a write counter of your data structure and yields the results as usual. If a write happens in between you can throw an exception to signal the user of your enumerator to try again until he succeeds. This does work but delegates responsibility onto the caller of your enumerable that he needs retry the enumeration until it succeeds.

like image 41
Alois Kraus Avatar answered Oct 28 '22 12:10

Alois Kraus