Net, методи for () і foreach ()

Метод For ()

У TPL паралелізм даних підтримується, зокрема, за допомогою методу For (), визначеного в класі Parallel. Цей метод існує в декількох формах. Його розгляд ми почнемо з найпростішої форми, наведеної нижче:

де fromInclusive позначає початкове значення того, що відповідає змінної управління циклом; воно називається також ітераційним, або індексним, значенням; a toExclusive - значення, на одиницю більше кінцевого. На кожному кроці циклу змінна управління циклом збільшується на одиницю. Отже, цикл поступово просувається від початкового значення fromInclusive до кінцевого значенням toExclusive мінус одиниця.

Циклічно виконується код вказується методом, переданим через параметр body. Цей метод повинен бути сумісний з делегатом Action.

Для методу For () узагальнений параметр T повинен бути, звичайно, типу int. Значення, яке передається через параметр obj, буде наступним значенням змінної управління циклом. А метод, який передається через параметр body, може бути іменованих або анонімним. Метод For () повертає екземпляр об'єкта типу ParallelLoopResult, що описує стан завершення циклу. Для простих циклів цим значенням можна знехтувати.

Головна особливість методу For () полягає в тому, що він дозволяє, коли така можливість є, распараллелить виконання коду в циклі. А це, в свою чергу, може привести до підвищення продуктивності. Наприклад, процес перетворення масиву в циклі може бути розділений на частини таким чином, щоб різні частини масиву перетворювалися одночасно. Слід, однак, мати на увазі, що підвищення продуктивності не гарантується через відмінності в кількості доступних процесорів в різних середовищах виконання, а також через те, що розпаралелювання дрібних циклів може скласти витрати, які перевищують заощаджений час.

У наведеному нижче прикладі програми демонструється застосування методу For () на практиці. На початку цієї програми створюється масив data, що складається з 1.000.000.000 цілих значень. Потім викликається метод For (), якому в якості "тіла" циклу передається метод MyTransform (). Цей метод складається з ряду операторів, що виконують довільні перетворення в масиві data. Його призначення - зімітувати конкретну операцію. Як буде докладніше пояснено трохи нижче, виконувана операція повинна бути нетривіальною, щоб паралелізм даних приніс якийсь позитивний ефект. В іншому випадку послідовне виконання циклу може завершитися швидше:

Ця програма складається з двох циклів. У першому, стандартному, циклі for инициализируется масив data. А в другому циклі, що виконується паралельно методом For (), над кожним елементом масиву data виробляється перетворення. Як згадувалося вище, це перетворення носить довільний характер і вибрано лише для цілей демонстрації. Метод For () автоматично розбиває виклики методу MyTransform () на частини для паралельної обробки окремих порцій даних, що зберігаються в масиві. Отже, якщо запустити дану програму на комп'ютері з двома доступними процесорами або більше, то цикл перетворення даних в масиві може бути виконаний методом For () паралельно.

Слід, однак, мати на увазі, що далеко не всі цикли можуть виконуватися ефективно, коли вони распараллелівать. Як правило, дрібні цикли, а також цикли, що складаються з дуже простих операцій, виконуються швидше послідовним способом, ніж паралельним. Саме тому цикл for ініціалізації масиву даних не распараллеливается методом For () в розглянутій тут програмі.

Розпаралелювання дрібних і дуже простих циклів може виявитися неефективним тому, що час, потрібний для організації паралельних завдань, а також час, що витрачається на перемикання контексту, перевищує час, що економить завдяки паралелізму. На підтвердження цього факту в наведеному нижче прикладі програми створюються послідовний і паралельний варіанти циклу for, а для порівняння на екран виводиться час виконання кожного з них:

Net, методи for () і foreach ()

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

Зверніть далі увагу на те, що паралельний варіант циклу перетворення даних виконується швидше, ніж послідовний. В даному випадку економія від розпаралелювання з лишком відшкодовує витрати на його додаткову організацію.

Як правило, відносно переваг, які дає розпаралелювання різних видів циклів, слід керуватися поточними рекомендаціями корпорації Microsoft. Крім того, необхідно переконатися в тому, що розпаралелювання циклу дійсно призводить до підвищення продуктивності, перш ніж використовувати такий цикл в остаточно випускається прикладному коді.

Що стосується наведеної вище програми, то необхідно згадати про двох інших її особливості. По-перше, зверніть увагу на те, що в паралельно виконується циклі для ініціалізації даних застосовується лямбда-вираз. Тут "тіло" циклу вказується в лямбда-виразі. (Нагадаємо, що в лямбдавираженіі створюється анонімний метод.) Отже, для паралельного виконання методом For () зовсім не обов'язково вказувати іменований метод.

І по-друге, зверніть увагу на застосування класу Stopwatch для обчислення часу виконання циклу. Цей клас знаходиться в просторі імен System.Diagnostics. Для того щоб скористатися ним, достатньо створити екземпляр його об'єкта, а потім викликати метод Start (), початківець звіт часу, і далі - метод Stop (), завершальний відлік часу. А за допомогою методу Reset () відлік часу скидається в початковий стан.

Тривалість виконання можна отримати різними способами. У розглянутій тут програмі для цієї мети використано властивість Elapsed. повертає об'єкт типу TimeSpan. За допомогою цього об'єкта і властивості TotalSeconds час відображається в секундах, включаючи і частки секунди. Як показує приклад розглянутої тут програми, клас Stopwatch виявляється вельми корисним при розробці паралельно виконуваного коду.

Як згадувалося вище, метод For () повертає екземпляр об'єкта типу ParallelLoopResult. Це структура, в якій визначаються два наступних властивості:

Властивість IsCompleted матиме логічне значення true, якщо виконані всі кроки циклу. Іншими словами, при нормальному завершенні циклу це властивість буде містити логічне значення true. Якщо ж виконання циклу перерветься завчасно, то ця властивість буде містити логічне значення false. Властивість LowestBreakIteration буде містити найменше значення змінної управління циклом, якщо цикл перерветься завчасно викликом методу ParallelLoopState.Break ().

Для доступу до об'єкту типу ParallelLoopState слід використовувати форму методу For (), делегат якого приймає в якості другого параметра поточний стан циклу. Нижче ця форма методу For () приведена в найпростішому вигляді:

В даній формі делегат Action, що описує тіло циклу, визначається наступним чином:

Для методу For () узагальнений параметр T1 повинен бути типу int, а узагальнений параметр Т2 - типу ParallelLoopState. Всякий раз, коли делегат Action викликається, поточний стан циклу приймає в якості аргументу arg2.

Для передчасного завершення циклу слід скористатися методом Break (). викликається для екземпляра об'єкта типу ParallelLoopState всередині тіла циклу, що визначається параметром body. Метод Break () оголошується наступним чином:

Виклик методу Break () формує запит на якомога більш раннє припинення паралельно виконуваного циклу, що може статися через кілька кроків циклу після виклику методу Break (). Але всі кроки циклу до виклику методу Break () все ж виконуються. Слід, також мати на увазі, що окремі частини циклу можуть і не виконуватися паралельно. Так, якщо виконано 10 кроків циклу, то це ще не означає, що всі ці 10 кроків представляють 10 перших значень змінної управління циклом.

Переривання циклу, паралельно виконуваного методом For (), нерідко виявляється корисним при пошуку даних. Так, якщо шукане значення знайдено, то продовжувати виконання циклу немає ніякої потреби. Переривання циклу може виявитися корисним і в тому випадку, якщо під час чергової операції зустрілися недостовірні дані.

Метод ForEach ()

де source позначає колекцію даних, які обробляються в циклі, a body - метод, який буде виконуватися на кожному кроці циклу. Як пояснювалося раніше, у всіх масивах, колекціях і інших джерелах даних підтримується інтерфейс IEnumerable. Метод, який передається через параметр body, приймає в якості свого аргументу значення або посилання на кожен опрацьований в циклі елемент масиву, але не його індекс. А в підсумку повертаються відомості про стан циклу.

Аналогічно методу For (), паралельне виконання циклу методом ForEach () можна зупинити, викликавши метод Break () для екземпляра об'єкта типу ParallelLoopState, переданого через параметр body, за умови, що використовується наведена нижче форма методу ForEach ():

Схожі статті