C# 2.0 iterators and lock

C

Clive Dixon

Are there any issues I should be worried about when using C# 2.0 iterators
in conjunction with lock, i.e.

public IEnumerator GetEnumerator()
{
lock (lockObject)
{
foreach (object obj in collection)
{
yield return obj;
}
}
}

When precisely is the lock/unlock happening with respect to the iteration?
Lock & unlock once only when the iterator object is created? Lock & unlock
with each iteration? Lock before first iteration & unlock on disposal after
last iteration?
 
J

Jon Skeet [C# MVP]

Are there any issues I should be worried about when using C# 2.0 iterators
in conjunction with lock, i.e.

public IEnumerator GetEnumerator()
{
    lock (lockObject)
    {
        foreach (object obj in collection)
        {
            yield return obj;
        }
    }

}

When precisely is the lock/unlock happening with respect to the iteration?
Lock & unlock once only when the iterator object is created? Lock & unlock
with each iteration? Lock before first iteration & unlock on disposal after
last iteration?

It would lock before the first, and after the last (or disposal). This
is a really bad thing to do - you shouldn't lock for indeterminate
amounts of time.

Jon
 
C

Clive Dixon

It would lock before the first, and after the last (or disposal). This
is a really bad thing to do - you shouldn't lock for indeterminate
amounts of time.

Hm, that's what I feared it would do; to confirm I wrote a class and
examined in Reflector. It looks to me however that the locking/unlocking is
done only within MoveNext (plus unlocking in Dispose in case an exception is
thrown), and not anywhere else. I really would have expected it to behave as
you say, but it looks like the locking behaviour changes once you use C# 2.0
iterators. Yuk.
 
J

Jon Skeet [C# MVP]

Hm, that's what I feared it would do; to confirm I wrote a class and
examined in Reflector. It looks to me however that the locking/unlocking is
done only within MoveNext (plus unlocking in Dispose in case an exceptionis
thrown), and not anywhere else. I really would have expected it to behaveas
you say, but it looks like the locking behaviour changes once you use C# 2.0
iterators. Yuk.

That's the behaviour I described though. It will lock before the first
value is yielded, and won't unlock until either MoveNext() gets to the
end or a Dispose call.

There's no change to behaviour here - lock always has try/finally
semantics, and the finally blocks are only executed at the relevant
times. In other words, the iterator block logically "pauses" when it
hits a yield return statement.

I recently wrote a fairly detailed analysis of how iterator blocks are
compiled. See http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx

Jon
 
C

Clive Dixon

That's the behaviour I described though. It will lock before the first
value is yielded, and won't unlock until either MoveNext() gets to the
end or a Dispose call.
There's no change to behaviour here - lock always has try/finally
semantics, and the finally blocks are only executed at the relevant
times. In other words, the iterator block logically "pauses" when it
hits a yield return statement.
I recently wrote a fairly detailed analysis of how iterator blocks are
compiled. See
http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx

OK I see now. It was too late in the day for me to concentrate. I see now in
Reflector that the state machine looks as though it calls Monitor.Enter/Exit
only in certain states. I did read chapter 6 of your book last night on
iterators but the relevance to locking didn't click with me because I had
misread the disassembled code. When you point out that lock has try/finally
semantics it becomes obvious in one of those head slapping moments.

Thanks for the article, and keep up the good work.
 
J

Jon Skeet [C# MVP]

OK I see now. It was too late in the day for me to concentrate.

I know that feeling :)
I see now in Reflector that the state machine looks as though it
calls Monitor.Enter/Exit only in certain states.

It doesn't help that the generated code uses try/fault which doesn't
exist in normal C# and can be easily misread as try/finally!
I did read chapter 6 of your book last night on iterators but the
relevance to locking didn't click with me because I had misread the
disassembled code. When you point out that lock has try/finally
semantics it becomes obvious in one of those head slapping moments.

Thanks for the article, and keep up the good work.

Do you think it would be explicitly worth mentioning locks in the
article? I talk about using statements, but not locks...
 
C

Clive Dixon

Do you think it would be explicitly worth mentioning locks in the
article? I talk about using statements, but not locks...

It might be worth mentioning Jon, it's a potential pitfall for the unwary.
 
J

Jon Skeet [C# MVP]

It might be worth mentioning Jon, it's a potential pitfall for the unwary.

Righto. I've added this paragraph:

<article>
It's worth remembering that most finally blocks in code aren't written
explicitly in C# - they're generated by the compiler as part of lock
and using statements. lock is particularly dangerous in iterator blocks
- any time you've got a yield return statement inside a lock block,
you've got a threading issue waiting to happen. Your code will keep
hold of the lock even when it has yielded the next value - and who
knows how long it will be before the client calls MoveNext() or
Dispose()? Likewise any try/finally blocks which are used for critical
matters such as security shouldn't appear in iterator blocks: the
client can deliberately prevent the finally block from executing if
they don't need any more values.
</article>

Do you think that covers it?
 
C

Clive Dixon

Do you think that covers it?

I think that should do the job - as you say, the important thing is the fact
that some try/finally are not explicitly stated in code and can whizz
straight over your head if you're not concentrating, like the lock example
did with me.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top