IEnumerable в Hashtable
От: Аноним  
Дата: 26.12.05 14:50
Оценка:
Здравствуйте, есть интересующий меня вопрос, по MSDN ответа не нашел, да и в форуме не видел.

есть следующий код, который работает:

Hashtable ht = Hashtable.Synchronized(new Hashtable());
ht.Add(1,1);
foreach(DictionaryEntry de in ht)
   Console.WriteLine(de.ToString());


а вот следующий код не работает:

Hashtable ht = Hashtable.Synchronized(new Hashtable());
ht.Add(1,1);
ICollection col = (ICollection)ht;
foreach(object de in col)
   Console.WriteLine(de.ToString());


первый вариант работает через IDictionaryEntry, а второй через IEnumerable. Если Hashtable поддерживает оба этих интерфейса, то почему второй вариант не работает (он просто не заходит в foreach)?

Заранее благодарен за компетентный ответ.

P.S. Я использую Framework 1.1
Re: IEnumerable в Hashtable
От: scif  
Дата: 26.12.05 15:28
Оценка: -4
Здравствуйте, Аноним, Вы писали:
А>
А>Hashtable ht = Hashtable.Synchronized(new Hashtable());
А>ht.Add(1,1);
А>ICollection col = (ICollection)ht;
А>foreach(object de in col)
А>   Console.WriteLine(de.ToString());
А>


Вобщем то и нет ничего удивительно, ведь интерфейс ICollection НЕ ПРЕДОСТАВЛЯЕТ
индексного доступа к елементам коллекции, поскольку не смотря на свое обманчивое имя
содержит только базовые свойства и методы для всех коллекций.
И даже то, что он наследует IEnumerable не дает ему желаемых возможностей, поскольку
IEnumerable содержит всего 1 метод GetEnumerator, который, правда, сможет Вам помочь.
Но это уже совсем другая история...
Re[2]: IEnumerable в Hashtable
От: Аноним  
Дата: 26.12.05 15:45
Оценка:
Здравствуйте, scif, Вы писали:

S>Здравствуйте, Аноним, Вы писали:

А>>
А>>Hashtable ht = Hashtable.Synchronized(new Hashtable());
А>>ht.Add(1,1);
А>>ICollection col = (ICollection)ht;
А>>foreach(object de in col)
А>>   Console.WriteLine(de.ToString());
А>>

S>
S>Вобщем то и нет ничего удивительно, ведь интерфейс ICollection НЕ ПРЕДОСТАВЛЯЕТ
S>индексного доступа к елементам коллекции, поскольку не смотря на свое обманчивое имя
S>содержит только базовые свойства и методы для всех коллекций.
S>И даже то, что он наследует IEnumerable не дает ему желаемых возможностей, поскольку
S>IEnumerable содержит всего 1 метод GetEnumerator, который, правда, сможет Вам помочь.
S>Но это уже совсем другая история...

Если посмотреть в ildasm, то можно увидеть что

IL_0020:  callvirt   instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.IEnumerable::GetEnumerator()
...
IL_0029:  callvirt   instance object [mscorlib]System.Collections.IEnumerator::get_Current()


т.е. ICollectoin использует IEnumerable для получение IEnumerator. А вот IEnumerator::get_Current() возвращает ошибку

    Current    <error: an exception of type: {System.InvalidOperationException} occurred>    System.Object


хотя col.Counter = 1.

Такое впечатление что Hashtable этот интерфейс не реализовывает. Если так то зачем его было наследовать ???
Re[3]: IEnumerable в Hashtable
От: scif  
Дата: 26.12.05 16:28
Оценка: 2 (1) -2
Здравствуйте, Аноним, Вы писали:
А>
А>IL_0020:  callvirt   instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.IEnumerable::GetEnumerator()
А>...
А>IL_0029:  callvirt   instance object [mscorlib]System.Collections.IEnumerator::get_Current()
А>


А>
А>    Current    <error: an exception of type: {System.InvalidOperationException} occurred>    System.Object
А>

Не торопитесь.
Дайте посмотрим на Ваш код:
//Создаем синхронизированую хештабличку,на основе пустой
Hashtable ht = Hashtable.Synchronized(new Hashtable());
//Добаляем в нее элемент
ht.Add(1,1);
//Приводим к интерфейсу
ICollection col = (ICollection)ht;
//Создаем энумератор
System.Collections.IEnumerator en=col.GetEnumerator();
//ресетим
en.Reset();
//Получаем текущий
Console.WriteLine(en.Current.ToString());

Только Вы не учли, что добавив в колекцию элемент — Вы ее рассинхронизировали,
а значит и энумератор НЕ ПОМОЖЕТ. Попробуем немножко перестроить ход наших мыслей
//Создаем пустую хештабличку
Hashtable ht = Hashtable();
//Добаляем в нее элемент
ht.Add(1,1);
//Приводим к интерфейсу спеуиального синхронного представителя нашей таблички.
ICollection col = (ICollection)ht.SyncRoot;
//Создаем энумератор
System.Collections.IEnumerator en=col.GetEnumerator();
//ресетим
en.Reset();
//Получаем текущий
Console.WriteLine(en.Current.ToString());

Вот так работает
Re[4]: IEnumerable в Hashtable
От: Аноним  
Дата: 26.12.05 17:05
Оценка:
Здравствуйте, scif, Вы писали:

S>Только Вы не учли, что добавив в колекцию элемент — Вы ее рассинхронизировали,


Бррр, это как, если не секрет? Если посмотреть рефлектором, статический метод Synchronized возвращает екземпляр Hashtable.SyncHashtable. Этот класс является просто оберткой, предоставляющий синхронизированный доступ к хештаблице.
Как можно рассинхронизировать коллекцию, добавив в нее элемент ?!

S>а значит и энумератор НЕ ПОМОЖЕТ. Попробуем немножко перестроить ход наших мыслей

S>//Создаем пустую хештабличку
S>Hashtable ht = Hashtable();
S>//Добаляем в нее элемент
S>ht.Add(1,1);
S>//Приводим к интерфейсу спеуиального синхронного представителя нашей таблички.
S>ICollection col = (ICollection)ht.SyncRoot;

//Рефлектор:
public virtual object SyncRoot
{
      get
      {
            return this;
      }
}


Так что "спеуиальный синхронный представитель нашей таблички" это есть сама хештаблица.

Не морочьте людям голову, если не знаете о чем говорите
Re[5]: IEnumerable в Hashtable
От: scif  
Дата: 26.12.05 17:28
Оценка:
Здравствуйте, Аноним, Вы писали:
А>//Рефлектор:
А>
А>public virtual object SyncRoot
А>{
А>      get
А>      {
А>            return this;
А>      }
А>} 
А>


А>Так что "спеуиальный синхронный представитель нашей таблички" это есть сама хештаблица.


А>Не морочьте людям голову, если не знаете о чем говорите


Уважаю желание людей разобраться во всем до конца

Ну чтож, открываем МСДН и смотрим
>When implemented by a class, gets an object that can be used to synchronize access to the ICollection
>Если реализовано в классе, возвращает объект который может быть использован для синхронного доступа к ICollection
Вот.
Так что В ДАННОМ СЛУЧАЕ "специальный синхронный представитель нашей таблички" это ДЕЙСТВИТЕЛЬНО И ЕСТЬ САМА ХЕШТАБЛИЦА (спасибо, научили рефлектором пользоваться). Но потенциально могут быть классы, в которых "специальный синхронный представитель" ЭТО НЕ ТОЖЕ что и объект.
Re[6]: IEnumerable в Hashtable
От: scif  
Дата: 26.12.05 17:36
Оценка:
И вот еще:
>Бррр, это как, если не секрет? Если посмотреть рефлектором, статический метод Synchronized возвращает екземпляр >Hashtable.SyncHashtable. Этот класс является просто оберткой, предоставляющий синхронизированный доступ к хештаблице.
>Как можно рассинхронизировать коллекцию, добавив в нее элемент ?!

Если делать это в одном потоке то практически нельзя. Однако при многопоточном доступе с изменением таблички, и не только добавлением элементов однозначно сказать что данные синхронизированы НЕЛЬЗЯ.
Следовательно почему ЛЮБЫЕ ИЗМЕНЕНИЯ ТАБЛИЧКИ не считать попыткой рассинхронизации?
Тут я майкросовтовцами абсолютно согласен.
Re[7]: IEnumerable в Hashtable
От: scif  
Дата: 26.12.05 18:17
Оценка:
Что то уж больно круто в предыдущем посте получилось....

>> статический метод Synchronized возвращает екземпляр Hashtable.SyncHashtable. Этот класс является просто оберткой.

Вот, Вы же сами это сказали — изменения основного набора данных производяться "от имени и по пручению" объекта-обертки.
Следовательно, два объекта-оберки созданные в разных потоках осуществлют доступ к одному и тому же набору данных вызывая методы HashTable, предварительно блокируя изменяемый объект.
А если Вы такой любитель покопаться в рефлекторе, обратите внимание на реализацию методов изменения коллекции HashTable.
Re: IEnumerable в Hashtable
От: AlexZu Россия  
Дата: 26.12.05 18:45
Оценка: +1
Здравствуйте, Аноним, Вы писали:

Налицо баг в реализации синхронизированной версии хэш-таблицы, обходной путь: не приводить к ICollection.
Остается надееться, что в .NET 2 починили.
Re: IEnumerable в Hashtable
От: Andrbig  
Дата: 27.12.05 07:15
Оценка:
Здравствуйте, Аноним, Вы писали:

А>первый вариант работает через IDictionaryEntry, а второй через IEnumerable. Если Hashtable поддерживает оба этих интерфейса, то почему второй вариант не работает (он просто не заходит в foreach)?


В классе HashTable.SyncHashTable (что является синронной оберткой над HashTable), есть GetEnumerator, поэтому правильно работает обычный foreach, но нет IEnumerable.GetEnumerator, поэтому не работает foreach от ICollection.

Баг чистой воды.
Re[2]: IEnumerable в Hashtable
От: MaximVK Россия  
Дата: 27.12.05 08:02
Оценка:
Здравствуйте, AlexZu, Вы писали:

AZ>Здравствуйте, Аноним, Вы писали:


AZ>Налицо баг в реализации синхронизированной версии хэш-таблицы, обходной путь: не приводить к ICollection.

AZ>Остается надееться, что в .NET 2 починили.

Согласен, т.к. коллекция начинает вести себя совершенно неожиданным образом. Баг редко проявляется, т.к. мало кто приводит Hashtable к ICollection.

Собственно говоря, баг в следующем.

Класс Hashtbale реализует интерфейс IEnumerable в котором есть метод GetEnumerator().
IEnumerator IEnumerable.GetEnumerator()
{
      return new Hashtable.HashtableEnumerator(this, 3);
}

Этот метод скрывается, т.к. подменяется методом.
public virtual IDictionaryEnumerator GetEnumerator()
{
      return new Hashtable.HashtableEnumerator(this, 3);
}


Как правильно сказал Аноним, статический метод возращает обертку Hashtable.SyncHashtable над Hashtable. В SyncHashtable виртуальный метод IDictionaryEnumerator GetEnumerator() честно переопределен. Поведение при этом не меняется, т.к.:
public override IDictionaryEnumerator GetEnumerator()
{
      return this._table.GetEnumerator(); //_table - это оборачиваемый Hashtable.
}


А вот метод IEnumerator IEnumerable.GetEnumerator() переопределить уже не можем, т.к. мы его скрыли.
А теперь посмотрим реализацию метода MoveNext() у Hashtable.HashtableEnumerator.
public virtual bool MoveNext()
{
      if (this.version != this.hashtable.version)
      {
            throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));
      }
      while (this.bucket > 0)
      {
            this.bucket--;
            object obj1 = this.hashtable.buckets[this.bucket].key;
            if ((obj1 != null) && (obj1 != this.hashtable.buckets))
            {
                  this.currentKey = obj1;
                  this.currentValue = this.hashtable.buckets[this.bucket].val;
                  this.current = true;
                  return true;
            }
      }
      this.current = false;
      return false;
}

Важный момент здесь — массив структур buckets, который объявлен в hashtable как private. В этом массиве храниться все данные хэштаблицы. Важно, что при работе с враппером Hashtable.SyncHashtable массив buckets меняется только в оборачиваемом классе, а в самой обертке остается пустым. Теперь посмотрим, что происходит:

Случай 1.
Hashtable ht = Hashtable.Synchronized(new Hashtable());
ht.Add(1,1);
foreach(DictionaryEntry de in ht)
   Console.WriteLine(de.ToString());

Здесь все честно, т.к. будет вызываться метод IDictionaryEnumerator GetEnumerator() корректно переопределенный в Hashtable.SyncHashtable. Соответственно, в метод MoveNext() всегда будет попадать внутренняя хештаблица.

Случай 2.
Hashtable ht = Hashtable.Synchronized(new Hashtable());
ht.Add(1,1);
ICollection col = (ICollection)ht;
foreach(object de in col)
   Console.WriteLine(de.ToString());

А вот здесь косяк, т.к. при приведении ht к ICollection будет вызван метод IEnumerable.GetEnumerator(), который не переопредлен. Соответственно в метод MoveNext() HashtableEnumerator-а попадет не внутренняя хештаблица, а сам враппер с пустым массивом buckets. В качестве экперимента, можно выполнить след. код, который должен корректно отработать.

Случай 2а.
Hashtable innerHt = new Hashtable();
Hashtable ht = Hashtable.Synchronized(innerHt);
ht.Add(1,1);
ICollection col = (ICollection)innerHt;
foreach(object de in col)
   Console.WriteLine(de.ToString());
Re[3]: IEnumerable в Hashtable
От: MaximVK Россия  
Дата: 27.12.05 08:18
Оценка:
Здравствуйте, MaximVK, Вы писали:

MVK>А вот метод IEnumerator IEnumerable.GetEnumerator() переопределить уже не можем, т.к. мы его скрыли.


Это я фигню сказал Видимо корректный код был бы такаой:
private class SyncHashtable : Hashtable, IEnumerable
{
  IEnumerator IEnumerable.GetEnumerator()
  {
      IEnumerable en = (IEnumerable)_table;
      return en.GetEnumerator();
  }
....
}
Re[2]: IEnumerable в Hashtable
От: CryptoPlus Украина  
Дата: 27.12.05 17:26
Оценка: +1
Здравствуйте, AlexZu, Вы писали:

AZ>Здравствуйте, Аноним, Вы писали:


AZ>Налицо баг в реализации синхронизированной версии хэш-таблицы, обходной путь: не приводить к ICollection.

AZ>Остается надееться, что в .NET 2 починили.

Вот именно,в .NET 2 работает отлично, а вот в .NET 1.1 — болт
Re[4]: IEnumerable в Hashtable
От: Аноним  
Дата: 27.12.05 18:03
Оценка:
Здравствуйте, scif

начал глубже копать, стал тестит с многопоточностью, и оказалось что Synchronized на самом деле не работает правильно.

Привожу код:

    public class Class1 
    {
        static Hashtable at = new Hashtable();
        static Hashtable al = Hashtable.Synchronized(at) ;
        class Class2
        {
            public void Start(){
                Thread th = new Thread(new ThreadStart(ThreadProc) ) ;
                th.Start() ;
            }
            
            private void ThreadProc(){
                Console.WriteLine(AppDomain.GetCurrentThreadId()) ;
                for (int i = 0; i < 2000; ++i){
                    Thread.Sleep(10);
                    at.Add(i,i);
                    Console.Write(".");
                }
            }
        }
        class Class3{
            public void Start(){
                Thread th = new Thread(new ThreadStart(ThreadProc) ) ;
                th.Start() ;
            }
            
            private void ThreadProc(){
                Console.WriteLine(AppDomain.GetCurrentThreadId()) ;
                //ждем 10 секунд пока не заполниться Hashtable на половину
                Thread.Sleep(10000) ;

                ICollection col = (ICollection)al.SyncRoot;
                foreach(DictionaryEntry de in col){
//если здесь поставить Thread.Sleep(1); то отработаеться один элемен и вылетит Exception
//а так успеет обработать несколько :)
                    Console.WriteLine(de.Value)  ;
                }
            }
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            Class2 cl2 = new Class2() ;
            Class3 cl3 = new Class3() ;
            
            cl2.Start() ;
            cl3.Start() ;
            
            Console.ReadLine();
        }
    }


в Class3 вылетает Exception. Может я как то не так понимаю нахначение этого Synchronized. Вроде написано что разрешает одновременный доступ на чтение и на запись.

Заранее благодарен.
Re[5]: IEnumerable в Hashtable
От: MaximVK Россия  
Дата: 27.12.05 18:18
Оценка: 1 (1)
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, scif


А>в Class3 вылетает Exception. Может я как то не так понимаю нахначение этого Synchronized. Вроде написано что разрешает одновременный доступ на чтение и на запись.


Исключение вылетает, потому что ты меняешь hashtable во время итерирования по enumerator-у. Это не имеет никакого отношения к многопоточности, т.е. в одном потоке будет аналогичная ситуация.
Re[5]: IEnumerable в Hashtable
От: scif  
Дата: 28.12.05 11:06
Оценка: -1
Здравствуйте, Аноним, Вы писали:

И опять же, все правильно для начала приведу фрагмент из МСДН в ремарках к Hashtable.Synchronized:

A Hashtable can safely support one writer and multiple readers concurrently. To support multiple writers, all operations must be done through this wrapper only.
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. To guarantee thread safety during enumeration, you can either lock the collection during the entire enumeration or catch the exceptions resulting from changes made by other threads.
Или:
Hashtable может безопасно поддерживать оброботку одного "писателя" и нескольких "читателей". Для поддержки нескольких писателей ОПЕРАЦИИ ДОЛЖНЫ ПРОИЗВОДИТЬСЯ ТОЛЬКО ЧЕРЕЗ "ВРАПЕРЫ".
Обход колекции по сути является не потокобезопасной процедурой. Даже когда колекция синхронизирована, другие потоки все еще могут ее модифицировать, ЧТО И ЗАСТАВЛЯЕТ ЭНУМЕРАТОР ВЫБРАСЫВАТЬ ИСКЛЮЧЕНИЕ.(как в Вашем случае). Для того чтобы обеспецить "потокобезопасность" во время пробега по коллекции, вы можете либо блокировать коллекцию на время просмотра, либо перехватывать исключения возникающие из-за действий других потоков.

Собственно, опираясь на вышенаписанное проводим небольшую корректировку

 for (int i = 0; i < 2000; ++i){
   Thread.Sleep(10);
   al.Add(i,i);//at.Add(i,i); ОПЕРАЦИИ ДОЛЖНЫ ПРОИЗВОДИТЬСЯ ТОЛЬКО ЧЕРЕЗ "ВРАПЕРЫ".
   Console.Write(".");
  }

........
  ICollection col = (ICollection)al.SyncRoot;
  lock(col){//вы можете блокировать коллекцию на время просмотра
     foreach(DictionaryEntry de in col){
        Console.WriteLine(de.Value);
     }
  }

Вот так работает
Re[6]: IEnumerable в Hashtable
От: Andrbig  
Дата: 28.12.05 12:54
Оценка:
Здравствуйте, scif, Вы писали:

S>
S>  ICollection col = (ICollection)al.SyncRoot;
S>  lock(col){//вы можете блокировать коллекцию на время просмотра
S>     foreach(DictionaryEntry de in col){
S>        Console.WriteLine(de.Value);
S>     }
S>  }
S>


Не согласен с выделенным, блокировки так не делают. За правильным способом обращайтесь в хелп по ICollection.SyncRoot.
Re[6]: IEnumerable в Hashtable
От: Аноним  
Дата: 28.12.05 13:51
Оценка:
Здравствуйте, scif

Отсюда делаем вывод что читать из hashtable можно только поэлементно, т.е. нельзя использовать foreach. Правильно?
Re[7]: IEnumerable в Hashtable
От: scif  
Дата: 28.12.05 14:08
Оценка:
Здравствуйте, Andrbig, Вы писали:

A>Здравствуйте, scif, Вы писали:


S>>
S>>  ICollection col = (ICollection)al.SyncRoot;
S>>  lock(col){//вы можете блокировать коллекцию на время просмотра
S>>     foreach(DictionaryEntry de in col){
S>>        Console.WriteLine(de.Value);
S>>     }
S>>  }
S>>


A>Не согласен с выделенным, блокировки так не делают. За правильным способом обращайтесь в хелп по ICollection.SyncRoot.


Ну может и не фонтан, но работает и так тоже...
Re[7]: IEnumerable в Hashtable
От: scif  
Дата: 28.12.05 14:15
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Отсюда делаем вывод что читать из hashtable можно только поэлементно, т.е. нельзя использовать foreach. Правильно?

Почему же нельзя, какие из моих высказываний об этом говорят?
Единственный нюанс: при многопоточном доступе читающий поток не увидит изменений внесенных другими потоками, до окончания чтения.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.