Страницы

Поиск по вопросам

понедельник, 12 ноября 2018 г.

Yield и потоки и все все все

Оператор 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

Комментариев нет:

Отправить комментарий