Оператор yield class UserCollection
{
public static IEnumerable Power()
{
yield return "Hello world!";
}
}
Оператор yield из .Net Reflector - перенесенный в рялии C# и Visual Studio class UserCollection
{
public static IEnumerable Power()
{
return new ClassPower(-2);
} private sealed class ClassPower : IEnumerable, IEnumerator, IEnumerator, IDisposable
{
// Поля.
private int state;
private object current;
private int initialThreadId; // Конструктор.
public ClassPower(int state)
{
this.state = state;
this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
} //private bool IEnumerator.MoveNext() // Так в Рефлекторе
bool IEnumerator.MoveNext()
{
switch (this.state)
{
case 0:
this.state = -1;
this.current = "Hello world!";
this.state = 1;
return true; case 1:
this.state = -1;
break;
}
return false;
} IEnumerator IEnumerable.GetEnumerator()
{
if ((Thread.CurrentThread.ManagedThreadId == this.initialThreadId) && (this.state == -2))
{
this.state = 0;
return this;
}
return new UserCollection.ClassPower(0);
} IEnumerator IEnumerable.GetEnumerator()
{
// Так в Рефлекторе
//return this.System.Collections.Generic.IEnumerable.GetEnumerator(); return (this as IEnumerable).GetEnumerator();
} void IEnumerator.Reset()
{
throw new NotSupportedException();
} void IDisposable.Dispose()
{
} // Свойства.
object IEnumerator.Current
{
get
{
return this.current;
}
} object IEnumerator.Current
{
get
{
return this.current;
}
}
}
}
Меня интересует строка и как ее комментирует автор this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
данная строка свидетельствует о синхронизации и доступа к этой коллекции как к разделяемому ресурсу.
Почему именно при работе с yield коллекция воспринимается как разделяемый ресурс и нужна вообще в принципе работа с потоками? См. рисунок как я понял работу оператора yield
и может т.к это в эту коробку все операторы yield помещают свое значение , то может поэтому нужна работа с потоками?
Ответ
Обратите внимание на функцию IEnumerable.GetEnumerator() Данный объект реализует как IEnumerable, так и IEnumerator. Состояние -2 означает, что объект «свежий», только что вернулся из вызова функции Power, и его энумерация ещё не начиналась. В этой ситуации, когда тут же вызывается GetEnumerator в том же потоке (а это происходит в подавляющем большинстве случаев: например, при вызове foreach (var x in Power())), то из соображений эффективности можно вернуть этот же объект (т. к. он служит и энумератором тоже). Но если энумерация уже прошла, внутреннее состояние объекта может быть испорчено, для этого случая возвращают новый объект. Точно так же если энумерация производится в другом потоке, то чтобы избежать необходимости синхронизации, лучше создать новый объект. Остановимся специально на последнем пункте. Если вдруг IEnumerable уйдёт в другой поток, и оттуда буден вызван метод GetEnumerator(), и одновременно в основном потоке будет тоже вызван этот же метод, то без проверки thread id может случиться так, что оба потока пройдут одновременно проверку this.state == -2, и получат один и тот же объект! В результате при энумерации они будут мешать друг другу. В этом случае мы даём «выиграть» тому же потоку, который создал объект. Таким образом, это специальная оптимизация для обычного случая (выделение только одного объекта); «необычные» случаи использования проходят более сложным путём. Подробнее по теме: Jon Skeet, C# in Depth. Iterator block implementation details: auto-generated state machines