Блокировка внутри GetEnumerator () ... что происходит в foreach с расширениями LINQ? - PullRequest
0 голосов
/ 29 сентября 2018

Данный пример класса:

internal Stuff<T> : IEnumerable<T> where T : Foo
{
    private readonly object Sync = new object();
    private readonly T[] TObjects;

    public IEnumerator<T> GetEnumerator()
    {
        lock(Sync)
            using (IEnumerator<T> safeEnum = TObjects.AsEnumerable().GetEnumerator())
                while (safeEnum.MoveNext())
                    yield return safeEnum.Current;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    //other things
}

Если я повторю этот класс в цикле foreach, весь цикл foreach блокируется схемой блокировки объекта.Мой вопрос: что произойдет с этим, если я использую это в запросе linq, например:

Stuff stuff = new Stuff() { /*some foos*/ };
var things = stuff.Where(/*some predicate*/);

foreach(Foo foo in things)
{
    //am i locked?
}

foreach(Foo foo in stuff.Where(/*some predicate*/))
{
    //am i locked?
}

Итак, все сводится к тому, как работает связывание запросов под капотом?

1 Ответ

0 голосов
/ 30 сентября 2018

Давайте рассмотрим ваш код подробно, кроме небольшой опечатки, которая stuff<T> должна быть классом, ваша реализация IEnumerator<T> выглядит следующим образом:

public IEnumerator<T> GetEnumerator()
{
    lock (Sync)
     using (IEnumerator<T> safeEnum = TObjects.AsEnumerable().GetEnumerator())
        while (safeEnum.MoveNext())
          yield return safeEnum.Current;
}

Это реализация для Enumeration, а не просто Enumerator, поскольку после получения Enumerator вы продолжаете обрабатывать коллекцию, перемещая Enumerator дальше, в идеале ваша пользовательская реализация должна выглядеть следующим образом:

IEnumerator IEnumerable.GetEnumerator() => TObjects.AsEnumerable().GetEnumerator(), поскольку перечисление выполненос помощью цикла foreach, который делает это путем доступа к реализованному перечислителю и вызова метода MoveNext и свойства Current, поэтому текущая реализация могла бы быть более краткой


Реализация перечислителя

TObjects.AsEnumerable().GetEnumerator() реализация выглядит следующим образом, она обращается к реализации GetEnumerator() для статического класса System.Linq.Enumerable, который внутренне имеет реализацию абстрактного класса Iterator<T>.

  1. AsEnumerable() - это простой метод расширения

     public static IEnumerable<TSource> AsEnumerable<TSource>(this IEnumerable<TSource> 
     source)
     {
        return source;
     }
    
  2. Теперь, поскольку наш источник является массивом T[], мы являемсяприменяя предложение Where, следовательно, как указано в следующей реализации Enumerable

     public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
        if (source == null) throw Error.ArgumentNull("source");
        if (predicate == null) throw Error.ArgumentNull("predicate");
        if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate);
        if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate);
        if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);
        return new WhereEnumerableIterator<TSource>(source, predicate);
    }
    

Мы получаем WhereArrayIterator, реализация которого для перечисления / итерации следующая:

    class WhereArrayIterator<TSource> : Iterator<TSource>
    {
        TSource[] source;
        Func<TSource, bool> predicate;
        int index;

        public WhereArrayIterator(TSource[] source, Func<TSource, bool> predicate) {
            this.source = source;
            this.predicate = predicate;
        }

        public override Iterator<TSource> Clone() {
            return new WhereArrayIterator<TSource>(source, predicate);
        }

        public override bool MoveNext() {
            if (state == 1) {
                while (index < source.Length) {
                    TSource item = source[index];
                    index++;
                    if (predicate(item)) {
                        current = item;
                        return true;
                    }
                }
                Dispose();
            }
            return false;
        }

        public override IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) {
            return new WhereSelectArrayIterator<TSource, TResult>(source, predicate, selector);
        }

        public override IEnumerable<TSource> Where(Func<TSource, bool> predicate) {
            return new WhereArrayIterator<TSource>(source, CombinePredicates(this.predicate, predicate));
        }
    }

Как уже упоминалось в комментариях, получение перечислителя в коллекцию является потокобезопасным, так как доступно только для чтения.Все упомянутые сведения о коде и классе можно найти в справочном источнике MS , который имеет поисковый браузер API

...