Многопоточные приложения в Visual Studio.NET. Разработка приложения, демонстрирующего многопоточность для одного процессора
Автор работы: Пользователь скрыл имя, 09 Декабря 2013 в 22:08, курсовая работа
Описание работы
Современные операционные системы (OC) нацелены на наиболее эффективное использование ресурсов компьютера. По большей части эффективность достигается за счет разделения ресурсов компьютера между несколькими процессами. Многопоточность является естественным продолжением многозадачности, точно также как виртуальные машины, позволяющие запускать несколько ОС на одном компьютере, представляют собой логическое развитие концепции разделения ресурсов. В многопоточном приложении одновременно работает несколько потоков. Иногда вместо термина "поток" используется термин "нить". Потоки – это независимые друг от друга задачи, выполняемые в контексте процесса. Поток использует код и данные родительского процесса, но имеет свой собственный уникальный стек и состояние процессора, включающее указатель команд.
Содержание работы
РЕФЕРАТ…………………………………………………….…………
4
СПИСОК СОКРАЩЕНИЙ…………………………………………….
5
СОДЕРЖАНИЕ…………………………………………………………
6
ВВЕДЕНИЕ…………………………………………………………….
7
1 КОНЦЕПЦИЯ МНОГОПОТОЧНОСТИ……………………………
9
2 БИБЛИОТЕКА ПАРАЛЛЕЛЬНЫХ ЗАДАЧ (TPL) ………………..
11
2.1 Основные нововведения TPL……………………………………
11
2.2 Параллелизм данных……………………………………………
17
2.3 Параллелизм задач……………………………………………….
20
2.4 Потенциальные ошибки, связанные с параллелизмом
данных и задач……………………………………………………
21
3 СОЗДАНИЕ МНОГОПОТОЧНОГО ПРИЛОЖЕНИЯ В СРЕДЕ
VISUAL STUDIO.NET………………………………………………
23
3.1 Структурная схема программы …………………………………
23
3.2 Разработка и оптимизация кода программы на C#…………….
25
4 ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЯ………………………………..
30
ЗАКЛЮЧЕНИЕ………………………………………………………..
35
Литература…………………………………………………………
Файлы: 1 файл
Курсяк C#.docx
— 192.86 Кб (Скачать файл)Задачи имеют над потоками два
основных преимущества. Во-первых, это
более масштабируемое и эффективное
использование системных
Еще одним отличием класса Task от класса Thread является изменение подхода к идентификации потока. Если для класса Thread использовалось свойство Name, доступное как для записи, так и для чтения, то в классе Task это свойство отсутствует. Взамен ему было добавлено свойство Id, принадлежащее типу int и доступное только для чтения. Оно объявляется следующим образом.
public int Id { get; }
Свойство Id имеет ряд преимуществ
над свойством Name. Во-первых, если
объекту типа Thread не задать свойство
Name вручную (задается
Также в класс Task было включено свойство Result. Оно необходимо для того, чтобы можно было организовать возврат значения из задачи.
public TResult Result { get; internal set; }
Данное свойство доступно только для чтения вне исполняемой задачи, так как аксессор set является для данного свойства внутренним. Такой механизм похож на всем хорошо известный механизм возврата значения из функции, и достаточно легко понимаем.
Создание задачи и запуск
задачи происходит путем
public Task (Action act);
public void Start();
Параметр act – точка входа в код, представляющий задачу. Подобным же образом происходит и создание потока посредством класса Thread. Но библиотека параллельных задач добавила несколько более эффективных методов создания задачи. Речь идет о методе StartNew(), определенном в классе TaskFactory.
public Task StartNew (Action act);
Объект класса TaskFactory может быть получен из свойства Factory, доступного только для чтения в классе Task. В данном методе сначала создается экземпляр класса Task для действия, определяемого параметром act, а затем сразу же осуществляется запуск задачи на исполнение. Использование метода StartNew() является эффективным в тех случаях, когда задача создается и сразу же запускается, ведь в данной ситуации не нужно вызывать метод Start().
В TPL (так же как и в
стандартных способах
Еще одним приятным нововведением библиотеки параллельных задач является «семейство» методов ожидания. Если в классе Thread была определен метод Join(), ожидающий завершения потока, для которого он был вызван, то в классе Task наипростейшим методом ожидания является метод Wait().
public void Wait();
Разницы между функциями
Join() и Wait() нет абсолютно никакой
(разве что название wait наиболее
понятно описывает смысл
public static bool WaitAll (params Task[] tsk);
public static int WaitAny (params Task[] tsk);
Метод WaitAll() ожидает завершения группы задач, и возврат из нее будет произведен только тогда, когда будут завершены все задачи. Метод WaitAny() ожидает завершения любой одной задачи из указанных в параметре tsk. Если во время выполнения задача сама сгенерировала исключение, или ее отменили, то будет сгенерирована исключительная ситуация AggregateException. Но следует отметить, что указанные в данной работе объявления функция Wait(), WaitAll(), WaitAny() далеко не единственные. Существует несколько вариантов объявления данных методов, где в параметрах можно указывать период простоя, отслеживать признак отмены (будет рассмотрен далее) и т.д. Данная группа методов обеспечивает довольно гибкую систему ожидания завершения задач и, существенно, облегчает работу программиста, сокращая объем кода многопоточного приложения.
Порой ресурсы, выделенные на поток, необходимо
использовать до завершения программы,
а в классе Thread «сборка мусора»
осуществляется только по завершении
работы приложения. TPL решила данную задачу
и позволила освобождать
public void Dispose();
Этот метод освобождает
Новаторской особенностью
public Task ContinueWith (Action cont_act);
Этот механизм довольно удобен для запуска ряда последовательно выполняющихся задач. Более того, метод ContinueWith() устраняет необходимость ожидания завершения задачи, которая впоследствии была продолжена. Существует и другие методы, определенные в классе TaskFactory, которые позволяют более гибко использовать возможность продолжения задачи.
public static Task ContinueWhenAny (Task [] tsk, Action< Task [] > cont_act);
public static Task ContinueWhenAll (Task [] tsk, Action< Task [] > cont_act);
Метод ContinueWhenAny() запускает новую задачу, как только завершилась одна из указанных в параметре tsk. А метод ContinueWhenAll() создает и начинает исполнение задачи лишь тогда, когда завершилось исполнение всех задач, перечисленных в tsk.
И последнее нововведение TPL,
на которое хотелось бы
Отмена задачи, как правило,
осуществляется следующим
public bool IsCansellationRequested { get; }
Если данное свойство
public void ThrowIfCansellationRequested()
Благодаря этому в отменяющем коде становится известно, что задача отменена. Для того чтобы удостовериться, что задача действительно была отменена, можно использовать свойство IsCanseled, которое возвращает значение true в случае, если задача отменена.
Все нововведения TPL, описанные в этой части данной работы, применяются в ситуациях, где библиотека параллельных задач используется таким же образом, как и класс Thread. Но TPL имеет и ряд других средств. Речь пойдет о классе Parallel, который упрощает параллельное исполнение кода и предоставляет методы, рационализирующие два подхода к построению многопоточного приложении – параллелизм данных и параллелизм задач.
2.2 Параллелизм данных
Параллелизм данных заключается в параллельной обработке некоторой совокупности данных. Суть данного подхода в том, что операция над совокупностью данных (массив, коллекция и т.п.) разбивается на несколько потоков, в каждом из которых обрабатывается часть данных. Довольно легко заметить, что данный подход значительно ускоряет обработку данных, нежели последовательное взаимодействие. Нельзя сказать, что параллелизм данных не был возможен ранее средствами класса Thread. Возможность такой организации обработки данных была, однако она требовала немало усилий и времени. Библиотека TPL значительно упростила этот процесс.
Параллелизм данных в библиотеке параллельных задач осуществляется с помощью методов For() и ForEach(), определенных в классе Parallel.
Метод For() используется для того, чтобы
распределить на несколько процессоров
(если такая возможность имеется)
исполнение кода в цикле. Но следует
быть осторожным, так как использование
данного метода может как повысить,
так и понизить производительность
приложения. Понижение производительности
будет происходить в тех
Существует несколько
public static ParallelLoopResult For (int from, int to, Action act);
public static ParallelLoopResult For (int from, int to, Action act);
Первым параметром передается начальное состояние переменной управления циклом. Второй параметр – значение, на единицу больше конечного. Параметр act – это тот метод (может быть как именованным, так и анонимным), который будет исполняться на каждом шаге цикла. В первом объявлении метод act должен принимать переменную типа int, через которую будет передаваться текущее значение переменной управления циклом. Во втором случае метод act принимает еще и переменную типа ParallelLoopState для организации прерывания цикла.
Как видно из объявления
метода For(), данный метод возвращает
экземпляр объекта
public bool IsCompleted { get; }
public Nulable LowestBreakIteration { get; }
Свойство IsCompleted принимает логическое значение true в том случае, если корректно выполнены все шаги цикла. Если же выполнение цикла прервалось раньше времени, данное свойство содержит значение false. Свойство LowestBreakIteration будет содержать наименьшее значение переменной управления циклом, если цикл был прерван.
Преждевременное завершение цикла For() осуществляется с помощью метода Break(), определенного для объекта типа ParallelLoopState, который передается вторым параметром в метод act соответствующего объявления метода For().
public void Break();
Прерывание полезно в тех
случаях, когда производится
Еще следует обратить внимание на тот факт, что при использовании метода For() нельзя опираться на последовательность цикла. Если цикл выполнил 100 шагов, это не означает, что эти 100 шагов соответствуют первым 100 значениям переменной управления циклом.
Метод ForEach() очень похож по
функциональности на метод For(
public static ParallelLoopResult ForEach (IEnumerable data, Action act);
public static ParallelLoopResult ForEach (IEnumerable data, Action act);
Он так же возвращает экземпляр объекта типа ParallelLoopResult, и данный цикл так же можно прервать с помощью метода Break() для экземпляра объекта типа ParallelLoopState, который передается в функцию act вторым параметром.
Первым параметром метод
ForEach() принимает коллекцию данных,
обрабатываемых в цикле, а
2.3 Параллелизм задач
Параллелизм задач обеспечивает параллельное выполнение двух или более независимых задач. Параллелизм задач был доступен и средствами класса Thread (они описаны в работе Пономарёва А.А). Библиотека параллельных задач вносит ряд преимуществ в данный подход построения многопоточного приложения. Во-первых, TPL достаточно проста в применении. А во-вторых, использование библиотеки параллельных задач позволяет автоматически масштабировать приложение на несколько процессоров, без сложного управления потоками и задачами явным образом.
Класс Parallel, о котором уже упоминалось ранее, содержит метод Invoke(), позволяющий выполнять один или несколько методов параллельно.
public static void Invoke (params Action[] acts);
Каждый метод, который передается методу Invoke() не должен ни принимать, ни возвращать значение. Метод Invoke() сначала инициализирует выполнение, а потом ожидает завершения выполнения всех передаваемых ему методов. Это избавляет программиста от необходимости использовать метод Wait(), ведь функцию ожидания метод Invoke() берт на себя. Но с этим связан довольно весомый недостаток, ведь метод Invoke() обеспечивает параллельное выполнения лишь методов, указанных в параметрах, а вот метод-родитель приостанавливается. Следовательно, можно сделать вывод, что построить параллельное выполнение потока-родителя и дочернего потока с помощью метода Invoke() нельзя.