Базовые методы работы с потоками в C# .NET
Лабораторная работа № 1. Введение в многопоточность. Многопоточность в C# .NET.
Одной из очень важных особенностей языка C# является то, что он имеет встроенную поддержку многопоточного программирования. Особенность многопоточной программы является то, что она может состоять из нескольких частей, каждая из которых выполняет свою часть поставленной задачи. Таким образом, части выполняются параллельно. Такая часть программы называется потоком. Среда .NET Framework, в свою очередь, содержит ряд классов, предназначенный для гибкой реализации многопоточных приложений. Причем, в отличии от других языков программирования, имевших слабые места или подводные камни , встречавшиеся в процессе создания многопоточных приложений, C# , в силу своей встроенной поддержки многопоточности, позволяет полностью ликвидировать эти проблемы, или , в худшем случае, свести их к минимуму.
Многопоточность может быть ориентированная на потоки и процессы. Здесь важно понимать разницу, так как процесс, по сути является отдельно выполняемой программной. Т.е. здесь многопоточность основана на том, что выполняются две и более программы.
Поток (по-английски - это thread, буквально можно перевести как «нить») – это управляемая единица кода, выполняемая в адресном пространстве породившего его потока. Используя многопоточность, мы можем реализовать нашу программу таким образом, что бы один поток просчитывал графику в сцене, визуализировал ее и обновлял окно, а другой, в это же самое время просчитывал физические законы, которые происходят в сцене. Или представьте другой пример: программа должна заниматься просчетом математических алгоритмов. Вычисление одного уравнения занимает 10-15 секунд. И следовательно, так как программа выполняет строки кода последовательно, до тех пор пока вычисление не будет завершено, окно приложения не будет отвечать на запросы операционной системы, оно, грубо говоря «зависнет» на 10-15 секунд, пока не закончатся циклы математических вычислений. Как видно из примера – не одна современная сложная программа не может обойтись без многопоточности.
Потоки могут как выполняться, так и ожидать выполнения, быть временно приостановленными (после чего возобновленными) , заблокированными. Так же поток может просто завершиться. Все это – возможные состояния потока.
Многопоточность в среде .NET Framework реализована следующим образом: существуют два типа потоков: высокоприоритетный и низкоприоритетный.
Высокоприоритетный(foreground) поток в отличии от низкоприоритетного (или фонового - background), назначается, как тип потока по умолчанию, а так же не будет остановлен, в том случае, если все высокоприоритетные потоки к его процессе будут остановлены.
Умение написания многопоточных программ, сводиться к тому, что бы уметь эффективно разработать объектную модель программы, которая будет использовать в ходе решения задачи несколько отдельных потоков, а так же координировать работу этих потоков между собой. Такая координация работы потоков называется синхронизацией потоков. По сути, синхронизация – это специальное средство, оснащенное собственной подсистемой методов, и являющейся одной из главных составляющих многопоточного программирования.
В C#, классы, отвечающие за поддержку многопоточного программирования, определены в пространстве имен System.Threading.
Базовые методы работы с потоками в C# .NET.
Рассмотрим процесс создания потоков, используя в качестве примера разработку консольной программы. При старте программы будут запускаться на выполнение 4 потока, каждый из которых будет выводить свой номер в окно консоли.
При создании потоков установим для них различные приоритеты, таким образом, части потоков будет выделяться больше квантов времени процессора, и они будут доминировать при выводе своего номера в окно. Это можно будет наблюдать в ходе работы программы.
Итак, сначала создайте новый проект, назовем его Thread_Step_1. В качестве шаблона выберите консольное приложение. Сгенерированный оболочкой код первоначально выглядит следующим образом:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Thread_Step_1
{
class Program
{
static void Main(string[] args)
{
}
}
}
Для работы с потоками нам необходимо подключить пространстве имен System.Threading. Добавьте строку using System.Threading после строки using System.Text.
Непосредственно перед функцией Main добавим функцию WriteString, которая будет отвечать за вывод символов, назначенных данному потоку (мы будем назначать номер потока) на экран. В качестве параметра функция будет получать объект _Data, который впоследствии будет преобразован в строку в коде функции.
Код функции будет выглядеть следующим образом:
static void WriteString(object _Data)
{
//для получения строки используем преобразование типов:
// приводим переменную _Data к типу string и записываем
// в переменную str_for_out
string str_for_out = (string) _Data;
// теперь поток 1 тысячу раз выведит полученную строку (свой номер)
for (int i = 0; i <= 1000; i++)
Console.Write(str_for_out);
}
Теперь перейдем непосредственно к коду функции Main. Здесь реализуем следующий код:
1. Сначала мы создадим 4 потока, каждому из которых укажем, что они будут выполнять функцию WriteString.
2. Далее назначим потокам приоритеты.
3. После этого запустим все четыре потока, передав в качестве параметра их номера.
4. Далее останется только дождаться завершения все четырех потоков и ожидать ввода пользователем какого-либо символа, чтобы завершить выполнение программы.
Код функции Main теперь будет выглядеть следующим образом:
static void Main(string[] args) //точка входа в программу
{
//создаем 4 потока, в качестве параметров передаем имя Выполняемой функции
Thread th_1 = new Thread(WriteString);
Thread th_2 = new Thread(WriteString);
Thread th_3 = new Thread(WriteString);
Thread th_4 = new Thread(WriteString);
//расставляем приоритеты для потоков
th_1.Priority = ThreadPriority.Highest; // самый высокий
th_2.Priority = ThreadPriority.BelowNormal; // выше среднего
th_3.Priority = ThreadPriority.Normal; // средний
th_4.Priority = ThreadPriority.Lowest; // низкий
// запускаем каждый поток, в качестве параметра передаем номер потока
th_1.Start( "1");
th_2.Start( "2");
th_3.Start( "3");
th_4.Start( "4");
Console.WriteLine( "все потоки запущены ");
//Ждем заврешения каждого потока
th_1.Join();
th_2.Join();
th_3.Join();
th_4.Join();
Console.ReadKey(); // прочитать символ (пока пользователь не нажмет клавишу программа не завершиться (чтобы можно было успеть посмотреть результат)).
}
Откомпилируйте и запустите программу (F5). Пример результата работы программы можно увидеть на рисунке 1. Так же, необходимо помнить, что при каждом запуске программы, выводимый на экран результат будет отличаться от предыдущего. А если запустить созданное приложение не из среды Visual Studio (без отладки), а просто, из операционной системы, то, до вывода сообщения «Все потоки запущены», по несколько раз может успеть выполнится назначенная функция каждого потока.
Рисунок 1.
Как видно из рисунка, к моменту запуска последнего, 4-го потока, 1-й поток, имеющий Highest приоритет уже успел полность выполниться. Последний же, 4-й приоритет, запущеный в добавок еще и последним, выполниться уже безусловно после всех других потоков.
Лабораторная работа № 2. Разработка простого многопоточного компонента с помощью Visual C#
·
·
·
·
·Visual Studio 2008
Компонент BackgroundWorker заменяет аналогичный код из пространства имен System.Threading и расширяет его функциональные возможности; однако при необходимости исходное пространство имен System.Threading можно сохранить для обеспечения обратной совместимости и использования в будущем.
Примером компонента, в котором можно использовать многопоточность, служит компонент, производящий расчет заработной платы. Этот компонент может в одном потоке обрабатывать данные, введенные пользователем в базу данных, в то время как в другом потоке будут выполняться вычисления, потребляющие значительные ресурсы процессора. При запуске этих действий в отдельных потоках пользователю не нужно ждать, пока компьютер закончит вычисления, чтобы ввести следующие данные. В данном пошаговом руководстве создается простой многопоточный компонент, который выполняет одновременно несколько сложных вычислений.
Создание проекта
Приложение будет состоять из одной формы и компонента. Пользователь будет вводить значения и сообщать компоненту о необходимости начать вычисления. Форма будет получать из компонента значения и отображать их в элементах управления "Label". Компонент будет выполнять вычисления, занимающие процессор, и сообщать форме о завершении. Для хранения значений, полученных из интерфейса пользователя, в компоненте следует создать общие переменные. В компоненте следует также реализовать методы для выполнения вычислений на основе значений этих переменных.