Thread-safe структури даних. NET 4 (ч.2), Різне, Інтернет-технології, статті

BlockingCollection – ця потокобезпечна колекція називається блокує, оскільки діє за наступним принципом:



Зрозуміло, це далеко не повний опис її можливостей. Давайте з ними ознайомимося. Почнемо з базових речей. Зміна вмісту колекції проводиться за допомогою спеціальних методів:















  Блокуючі методи Неблокуючим методи

Додавання
Add(T);
Add(T, CancellationToken);
TryAdd(T)
TryAdd(T, Int32)
TryAdd(T, TimeSpan)
TryAdd(T, Int32, CancellationToken)
Витяг
Take()
Take(CancellationToken)
TryTake(out T)
TryTake(out T, Int32)
TryTake(out T, TimeSpan)
TryTake(out T, Int32, CancellationToken)

Як видно, для додавання і вилучення елементів є в т.ч. і неблокуючим методи (ті, що починаються із префікса “Try”). Тобто можливий неблокуючим доступ до колекції. При цьому Try * метод поверне false, якщо елемент неможливо додати або витягнути, і true в іншому випадку. Для обох типів методів є перевантажені версії з маркером скасування, за допомогою якого можна асинхронно припинити очікування.

BlockingCollection має механізм “завершення”. Колекція вважається “завершеною”, коли відомо, що дані в неї додаватися більше не будуть. Після “завершення” всі спроби додати дані приведуть до генерації виключення InvalidOperationException. Якщо спробувати витягти дані з порожньої “завершеною” колекції, то також буде згенеровано виняток. Відразу ж після створення колекція є “незавершеною”, а її “завершення” виконується за допомогою виклику методу CompleteAdding (). Таким чином, зміна стану колекції виконується вручну. Навіщо був придуманий такий механізм? З його допомогою забезпечується синхронізація роботи виробника і споживача, тобто ділянок коду, що додають і витягають дані з колекції. Виробник може сповістити про те, що нові елементи він додавати більше не буде. При цьому споживач знатиме, що не варто очікувати поповнення колекції. Нижче наведено приклад, що демонструє подібний підхід.

Давайте розглянемо трохи коду, що працює з BlockingCollection . Спочатку створимо екземпляр колекції, причому в конструкторі вкажемо її максимальну місткість – в даному випадку це 10 елементів. Втім, розмір можна явно не задавати, викликавши конструктор без параметрів – в такому разі колекція буде рости “необмежено”:


Код:
/ / Створюємо колекцію
BlockingCollection<int> collection = new BlockingCollection<int>(10);

Далі до колекції додамо кілька елементів, і на консоль виведемо властивості колекції (про які трохи пізніше):


Код:
collection.Add(200);collection.Add(300);collection.Add(400);collection.Add(500); Console.WriteLine(“Count: ” + collection.Count);Console.WriteLine(“BoundedCapacity: ” + collection.BoundedCapacity);Console.WriteLine(“IsCompleted: ” + collection.IsCompleted);Console.WriteLine(“IsAddingCompleted: ” + collection.IsAddingCompleted);Console.WriteLine();

Після цього запустимо таймер, в делегатові callback якого відбувається додавання елементів і “завершення” колекції:


Код:
Timer timer = new Timer(delegate
{
Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine (DateTime.Now.ToLongTimeString () + “Додавання” + “600”); collection.Add (600); Thread.SpinWait (300000000); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine (DateTime.Now.ToLongTimeString () + “Додавання” + “700”); collection.Add (700); collection.CompleteAdding ();
},
  null, 3000, Timeout.Infinite);

Делегат, вказаний при створенні таймера, буде викликаний в потоці, відмінному від того, в якому працює метод Main () – це дозволить промоделювати доступ до колекції з боку декількох потоків. Далі в циклі починаємо перебір елементів (щоб відрізнити висновок від різних потоків, використовується різний колір тексту):


Код:
foreach (var item in collection.GetConsumingEnumerable())
{
    Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine (DateTime.Now.ToLongTimeString () + “Отримання” + item);
}

Потім знову виведемо властивості колекції на консоль. Ось скріншот з результатами роботи програми:

Очевидно, що порядок вилучення елементів змінився, і тепер він відповідає LIFO, що характерно для стека. Т.ч. є можливість реалізувати сховище даних, найбільш підходяще в даному конкретному випадку, і використовувати його в BlockingCollection . Для цього досить успадковувати інтерфейс IProducerConsumerCollection :


Код:
public interface IProducerConsumerCollection<T> : IEnumerable<T>, ICollection, IEnumerable
{
    void CopyTo(T[] array, int index);
    T[] ToArray();
    bool TryAdd(T item);
    bool TryTake(out T item);
}

На закінчення хочу відзначити кілька цікавих статичних методів класу BlockingCollection . З їх допомогою можна додавати або вилучати елементи, взаємодіючи з будь-якої з колекцій, переданих в якості параметра. Традиційно, є 2 версії цих методів:



Як бачимо, вони беруть масив колекцій, і параметр – елемент, або out-параметр – витягають. Повертають ці методи індекс колекції в масиві, в яку додали дані, або, відповідно, витягли. При цьому блокують методи чекають появи вільного місця в колекції або нового елемента, блокуючи при цьому потік, в якому виконуються. Неблокуючим, залежно від викликаної перевантаженої версією, відразу повертають управління, або “катують щастя“Зазначений таймаут. Зрозуміло, у списку вище наведені не всі сигнатури методів – є, наприклад, беруть маркер скасування, CancellationToken, докладніше краще подивитися MSDN.

Коментарі до 1-ї частини цього огляду спонукали мене уважніше поставитися до розгляду повертаються методами значень, і не дарма. Вивчаючи статичні методи, я подумав – коли, наприклад, TakeAnyFrom () поверне -1? І, чесно кажучи, так і не зміг придумати сценарію. Якщо всі колекції масиву-параметра порожні – він буде вічно чекати їх поповнення або “завершення”. Однак, якщо хоча б одну з них завершити, отримаємо ArgumentException. Якщо ж покласти в хоча б одну колекцію дані, метод поверне її індекс і в out-параметрі ті самі дані. Рефлектор підказав, що насправді все статичні методи BlockingCollection викликають всередині один і той же метод. Але я так і не зрозумів, як при завданні нескінченного таймауту неблокуючим метод може повернути -1, тому переадресував питання на форум MSDN. Для тих, хто зацікавився, топік тут, А ось тут відповідна тема на MS Connect. Поки зійшлися на тому, що це просто помилка в документації – копіпаст з опису методу TryTakeAnyFrom ().

Інші приклади використання BlockingCollection можна знайти на сайті MSDN Code Gallery. Там є сторінка з вихідними кодами, на яких демонструється використання тих чи інших засобів розпаралелювання коду, що входять в. NET 4. На момент публікації приклади є тільки для. NET 4 beta 1, але скоро опублікують комплект і для beta 2. У складі цих прикладів є, наприклад, набір методів-розширень, що дозволяє “Перетворити” дану колекцію в об’єкт типу IProducerConsumerCollection .

Хочу звернути увагу, що найбільш актуальна реалізація даної колекції перебуває у складі. NET 4 beta 2, який став доступний на днях. Якщо виникне бажання випробувати її в справі, рекомендую встановити Visual Studio 2010 beta 2. У порівнянні з торішньою червневої CTP-версією, що працює під. NET 3.5, деякі методи були перейменовані. Втім, це стосується всієї бібліотеки Parallel Extensions. Повний опис BlockingCollection можна знайти у відповідному розділі MSDN.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*