лабараторная работа. Теория_к_лабораторным. 1. Применение параллельных вычислений
Скачать 0.7 Mb.
|
4. Конструкция Parallel.Invoke и пул потоковКонструкция Parallel.Invoke и пул потоков, по сути, предоставляют одни и те же возможности – позволяют выполнять функциональность, сгруппированную в отдельных методах, параллельно. И то и другое достаточно легко использовать. Пул потоков управляется системой, которая сама занимается их созданием и диспетчеризацией. Если у вас нет необходимости самостоятельно управлять потоками параллельных вычислений, а требуется в фоновом режиме выполнить ряд задач, то рекомендуется воспользоваться именно пулом потоков, а не тратить ресурсы на создание своего, выделенного только для ваших целей потока. При использовании пула потоков для ваших вычислений будет предоставлен поток из числа уже имеющихся в пуле, а время на их создание и инициализацию, в том числе на выделение системных ресурсов, потрачено не будет. Все потоки в пуле потоков являются фоновыми, а значит, принудительно завершаются системой после завершения последнего основного потока в процессе вашего приложения. Более подробно узнать про тонкости инициализации потоков можно в msdn, а на данный момент того, что было сказано пока достаточно. Использовать пул потоков не получится, когда вам требуется, например, создать основной поток, определить потоку более высокий или более низкий по отношению к значению по умолчанию приоритет, в соответствии с которым система будет выделять ему процессорное время. Также не получится самостоятельно приостанавливать и возобновлять работу потока. Не следует использовать пул потоков, когда ваши вычисления “слишком тяжелые”, что вызовет “захват и удержание” на долгое время ценных вычислительных ресурсов. Создание выделенных потоков будет рассмотрено в следующих примерах. Вернемся к самому примеру. Пользователю предлагается вводить произвольный текст. Обработчик события изменения текста будет запускать таймер, время срабатывания которого также можно определить посредством пользовательского интерфейса. Таймер через заданное время запустит с использованием конструкции Parallel.Invoke или пула потоков (определяется галочкой “Использовать пул потоков”) в фоновом режиме две процедуры обработки введенного текста: "замена цифр соответствующими словами" и "перевод текста в верхний регистр". Обе процедуры работают в независимых потоках и независимо друг от друга меняют содержимое элемента управления текстом. Ниже приведен пример работы программы: два ”скриншота”, сделанные с интервалом в одну секунду после ввода текста. 123456789-qwerty asbcd-12-34-12-YTREWQ-TT-12 Пример работы программы с конструкцией Parallel.Invoke. На первом рисунке видно, что результат перевода цифр в соответствующие им слова еще не переведен в верхний регистр. На втором рисунке уже виден окончательный результат фоновой обработки текста. Особо комментировать фрагменты кода опять же не буду, но по поводу работы таймера стоит отметить, что процедура TimerHandler также выполняется в одном из потоков пула, выбор которого осуществляет система. Это важно, поскольку обновление элементов управления возможно только из основного потока, и для этой цели следует использовать свойство InvokeRequired и метод Invoke обновляемого элемента управления (справедливо для Windows Forms). //Пример 2. Parallel.Invoke и пул потоков //Таймер private System.Threading.Timer m_Timer; //Вводимый текст private string m_Text; //Объект - блокировка для разграничения доступа к тексту private object m_TextLock = new object(); //Обработка события изменения вводомого текста private void textBoxParallelInvoke_TextChanged(object sender, EventArgs e) { //Период срабатывания таймера int _TimerDelay = System.Convert.ToInt32(numericUpDownParallelInvokeTimer.Value) * 100; //Обрабатываемый текст m_Text = textBoxParallelInvoke.Text; //Запуск/перезапуск вызова метода TimeHandler через _TimerDelay миллисекунд if (m_Timer == null) m_Timer = new System.Threading.Timer(TimerHandler, "ParallelInvoke", _TimerDelay , 0); else m_Timer.Change(_TimerDelay , 0); } //При закрытии формы необходимо 'обнулить' таймер private void Form_Closing(object sender, EventArgs args) { if (m_Timer == null) return; m_Timer.Dispose(); m_Timer = null; } В зависимости от значения checkBoxThreadPool фоновая обработка запускается посредством прямого использования пула потоков или с помощью конструкцииParallel.Invoke (см. код TimerHandler). Если свойство InvokeRequired возвращает true, то это означает, что текущий поток не является основным. Для выполнения программного кода обработки элемента пользовательского интерфейса в основном потоке необходимо вызвать метод Invoke и передать ему ссылку на обработчик (см. код UpdateTextHandler). //Обработка срабатывания таймера private void TimerHandler(object sender) { if (checkBoxThreadPool.Checked) {//Запуск параллельных вычислений посредством пула потоков ThreadPool.QueueUserWorkItem((object state) => { DigitsToString(); }); ThreadPool.QueueUserWorkItem((object state) => { TextToUpperCase(); }); } else//Запуск параллельных вычислений посредством конструкции Parallel.Invoke Parallel.Invoke(DigitsToString, TextToUpperCase); } //Обработчик события изменения m_Text //Гарантирует работу с textBoxParallelInvoke ТОЛЬКО в потоке пользовательского интерфейса private void UpdateTextHandler(object sender, EventArgs args) { try { if (this.InvokeRequired) {//Текущий поток - НЕ поток пользовательского интерфейса, поэтому //осуществляем рекурсивный вызов UpdateTextHandler для выполнения //обработки в потоке пользовательского интерфейса this.Invoke(new EventHandler(UpdateTextHandler)); return; } } catch { return; } //Текущий поток - поток пользовательского интерфейса. textBoxParallelInvoke.Text = m_Text; textBoxParallelInvoke.SelectionStart = textBoxParallelInvoke.Text.Length; } Ниже приведены сами процедуры фоновой обработки. Ничего сложного в них нет, но ради разнообразия и с целью показать, для чего разработчиками языка C# была введена уже знакомая вам конструкция потокобезопасного доступа к разделяемому ресурсу lock {}, в этом примере вместо нее я использовал методыMonitor.Enter и Monitor.Exit совместно с конструкцией try-finally. Именно эту громоздкую конструкцию и заменяет элегантная lock {}, пример использования которой был приведен в предыдущем примере. //Обработка изменения m_Text private void UpdateText() { try { Invoke(new EventHandler(UpdateTextHandler)); } catch { return; } } //Преобразование цифр в текстовое представление private void DigitsToString() { for (int i = 0; i < 10; i++) { //Потокобезопасная блокировка (аналог lock() {}) Monitor.Enter(m_TextLock); try { switch (i) { case 0: { m_Text = m_Text.Replace("0", "[zero]"); break; } case 1: { m_Text = m_Text.Replace("1", "[one]"); break; } case 2: { m_Text = m_Text.Replace("2", "[two]"); break; } case 3: { m_Text = m_Text.Replace("3", "[three]"); break; } case 4: { m_Text = m_Text.Replace("4", "[four]"); break; } case 5: { m_Text = m_Text.Replace("5", "[five]"); break; } case 6: { m_Text = m_Text.Replace("6", "[six]"); break; } case 7: { m_Text = m_Text.Replace("7", "[seven]"); break; } case 8: { m_Text = m_Text.Replace("8", "[eight]"); break; } case 9: { m_Text = m_Text.Replace("9", "[nine]"); break; } } } finally { //Гарантированный выход из блокировки Monitor.Exit(m_TextLock); } UpdateText(); //Пауза, необходимая для обновления пользовательского интерфейса. Thread.Sleep(10); } } //Перевод текста в 'верхний' регистр private void TextToUpperCase() { //Потокобезопасная блокировка (аналог lock() {}) Monitor.Enter(m_TextLock); try { m_Text = m_Text.ToUpper(); } finally { //Гарантированный выход из блокировки Monitor.Exit(m_TextLock); } UpdateText(); //Пауза, необходимая для обновления пользовательского интерфейса. Thread.Sleep(100); } |