ПОТОКИ Начальные сведения

advertisement
ПОТОКИ
Начальные сведения
Многопоточность
Каждому традиционному приложению Win32
соответствует один (обычно) или несколько
процессов (process).
Процесс — это единица, которая
характеризуется собственным набором
внешних ресурсов и выделенной приложению
областью оперативной памяти.
Для каждого файла ЕХЕ операционная система
создает отдельную изолированную область в
оперативной памяти, которой процесс
пользуется в течение всего своего жизненного
цикла.
Многопоточность
Каждому процессу соответствует один (по крайней
мере) или несколько потоков.
Поток (thread) можно представить как специфический
путь выполнения внутри процесса Win32 (thread в
буквальном переводе с английского означает
«нить»).
Первый поток, создаваемый в процессе, называется
первичным процессом (primary thread).
В любом процессе существует по крайней мере один
поток, который выполняет роль точки входа для
приложения. В традиционных графических
приложениях Windows такой точкой входа является
метод WinMain (), а в консольных приложениях —
метод main().
Многопоточность
Основная цель, для которой создаются
многопоточные приложения (вместо
использования единственного потока), —
повышение производительности и
сокращение времени отклика приложения.
Многопоточные приложения на
однопроцессорном компьютере создают
иллюзию одновременного выполнения сразу
нескольких дел.
Управление потоками производится на уровне
операционной системы.
Многопоточность
Компьютеры с единственным центральным процессором в
действительности не могут одновременно обрабатывать
более одного потока.
Иллюзия многозадачности достигается тем, что каждому
потоку выдаются (в соответствии с его приоритетом)
специальные кванты времени (time-slice).
При исчерпании потоком выделенного ему кванта времени
ресурсы центрального процессора передаются другим
потокам, а первый поток вновь ждет своей очереди.
Для того чтобы поток мог продолжить выполнение с того
места, на котором его работа была остановлена, он
обеспечивается возможностью записи в локальную
память потока (Thread Local Storage) и отдельным стеком
вызовов.
Пространство имен System.Threading
Тип
Назначение
Interlocked
Для синхронизированного доступа к общим данным
Monitor
Обеспечивает синхронизацию потоковых объектов при
помощи блокировок и управления ожиданием
Mutex
Примитив синхронизации, используемый для
синхронизации разных процессов
Thread
Представляет поток, работающий в среде выполнения
.NET При помощи этого типа можно порождать в текущем домене
приложения новые потоки
ThreadPool
Используется для управления набором взаимосвязанных
потоков
Timer
Определяет делегат, который будет вызван в указанное
время. Операция ожидания выполняется потоком в пуле потоков
WaitHandle
Представляет во время выполнения все объекты
синхронизации (которые позволяют многократное ожидание)
ThreadStart
Представляет делегат со ссылкой на метод, который
должен быть выполнен перед запуском потока
TimerCallback Делегат для объектов Timer
WaitCallback
Делегат, который представляет метод обратного вызова
для рабочих элементов ThreadPool
Работа с классом Thread
Самый простой тип в пространстве имен System.Threading — это класс
Thread. Этот класс в .NET — не более чем объектная оболочка вокруг
некоторого этапа выполнения программы внутри домена приложения.
Статические переменные и методы
Назначение
CurrentThread
Это свойство только для чтения возвращает
ссылку на поток, выполняемый в настоящее время
GetData() SetData()
Возвращает/устанавливает значение для
указанного слота в текущем потоке
GetDomain()
GetDomainlD()
Возвращает ссылку на домен приложения
(идентификатор домена приложения), в рамках которого работает
указанный поток
Sleep()
Приостанавливает выполнение текущего потока
на указанное пользователем время
Работа с классом Thread
Обычные члены типа Thread
Переменные и методы Назначение
IsAlive
Это свойство возвращает «true» или «false» в
зависимости от того, запущен поток или нет
IsBackground Свойство предназначено для получения или установки
значения, которое показывает, является ли этот поток фоновым
Name
Свойство для установки дружественного текстового
имени потока
Priority
Позволяет получить/установить приоритет потока
(используются значения из перечисления ThreadPriority)
ThreadState
Возвращает информацию о состоянии потока
(используются значения из перечисления ThreadState)
Interrupt()
Прерывает работу текущего потока
Join()
Ждет появления другого потока (или указанный
промежуток времени) и завершается
Resume()
Продолжает работу после приостановки работы потока
Start()
Начинает выполнение потока, определенного делегатом
ThreadStart
Suspend()
Приостанавливает выполнение потока.
Пример
public class Car
{
string name;
int rate;
Random r= new Random();
public Car(string name, int rate)
{
this.name=name;
this.rate=rate;
}
public override string ToString()
{
return("
"+this.name+", скорость="+this.rate);
}
public void Pusk()
{
for(int i=0; i<100; i++)
{
rate+= r.Next(30)-15;
if(rate<0) rate=0;
Console.WriteLine(this);
}
}
}
Пример
public class Road
{
static void Main(string[] args)
{
Car first = new Car("Камаз-бетономешалка", 100);
first.Pusk();
Car second = new Car("Ока-кабриолет", 50);
second.Pusk();
}
}
Результаты
Пример – создаем потоки
public class Road
{
static void Main(string[] args)
{
Car first = new Car("Камаз-бетономешалка", 100);
Thread firstThread =new Thread(new ThreadStart(first.Pusk));
firstThread.Start();
Car second = new Car("Ока-кабриолет", 50);
Thread secondThread =new Thread(new ThreadStart(second.Pusk));
secondThread.Start();
}
}
Результаты
Пример – держим паузу
public void Pusk()
{
for(int i=0; i<100; i++)
{
rate+= r.Next(30)-15;
if(rate<0) rate=0;
Console.WriteLine(this);
Thread.Sleep(500);
}
}
Результаты
ПОТОКИ
в клиент-серверных приложениях
Многопотоковый сервер
Часто серверное приложение вынуждено
работать сразу с несколькими клиентами
(пример – чат-сервер).
Тогда для каждого клиента следует создавать
отдельный поток.
Удобно разработать специальный класс для
этих целей (назовем его Handler).
Рассмотрим пример сервера-«эха», который
только и умеет - получать сообщение и
тут же отправлять его обратно.
Многопотоковый сервер
Server
Handler
Client 1
Handler
Client 2
Handler
Client 3
Handler
Client 4
Класс Handler
class Handler
{
Socket clientSocket;
NetworkStream netStream;
BinaryWriter binWriter;
BinaryReader binReader;
public Handler(Socket clientSocket)
{
this.clientSocket=clientSocket;
}
public void Reader()
{
// потоки для чтения-записи
netStream = new NetworkStream(clientSocket);
binWriter = new BinaryWriter(netStream);
binReader = new BinaryReader(netStream);
Класс Handler
// продолжение
while(true)
{
string message=binReader.ReadString();
Console.WriteLine("Received:"+message);
binWriter.Write("Received:"+message);
if (message.Equals("quit"))
{
Console.WriteLine("Client disconnected");
binWriter.Close();
netStream.Close();
break;
}
}
}
Что изменится на сервере
while( true )
{
Socket clientSocket = tcpListener.AcceptSocket();
if (clientSocket.Connected)
{
Handler hand = new Handler(clientSocket);
Thread mythread = new Thread(
new ThreadStart(hand.Reader));
mythread.Start();
}
Что изменится на клиенте
// Цикл передачи-приема сообщений выглядит
// следующим образом:
// клиент отправляет сообщение и ждет ответа,
// затем процесс повторяется
while( true )
{
binWriter.Write(Console.ReadLine());
Console.WriteLine(binReader.ReadString());
}
Задание
Создайте серверное и клиентское
приложения по примеру.
Процесс обмена сообщениями получился
бесконечным. Как его, наконец-то,
прекратить? Подумайте.
Подумайте также, как можно рассылать
сообщения всем клиентам (как обычно
и бывает в чате).
ПОТОКИ
на клиентской стороне
клиент-серверных приложений
Потоки на клиенте
 Как мы уже видели, на серверной стороне
возникает необходимость создавать
множество потоков при обмена данными со
множеством клиентов (отдельному клиентуотдельный поток).
 Это касается, в основном, приема
сообщений от клиентов, так как мы не можем
точно предугадать, в какой момент времени
от клиента придет очередное сообщение.
Потоки на клиенте
 Но та же самая проблема возникает и на
клиентской стороне! Поскольку обычно мы
тоже не можем точно предугадать, в какой
момент времени от сервера придет
очередное сообщение, приходится
организовывать специальный поток для
приема сообщений.
 Вариант решения проблемы: создаем
отдельный метод с бесконечным циклом, в
котором читается очередное сообщение от
сервера и на этот метод «навешиваем» поток.
Пример чат-клиента
Разместим в окне
клиентского приложения:
Текстовое поле say
Кнопку send
Текстовую область result
Создание потока
Thread potok = null; // переменные объекта
StreamWriter sw=null;
StreamReader sr=null;
…………………..
// После подключения к серверу организуем
// поток-оболочку для метода read:
potok = new Thread(new
ThreadStart(this.read));
potok.Start();
Метод read
………………..
do
{
// прочитать ответ
try
{
string msg = sr.ReadLine();
// записать в текстовую область
result.AppendText("Received:
"+msg+"\n");
}
catch(IOException ex) { …………………..}
} while ( (msg.ToLower()).CompareTo( "exit") != 0 );
……………………………..
Проблемы с доступом
Строка
result.AppendText("Received:
"+msg+"\n");
при выполнении вызовет исключение:
Exception System.InvalidOperationException was thrown
in debuggee:
Cross-thread operation not valid: Control ‘result'
accessed from a thread other than the thread it was
created on.
В чем же проблема? Дело в том, что мы пытаемся
выполнить перекрестную операцию между потоками:
получить доступ к элементу управления result,
который был создан в основном потоке.
Проблемы с доступом
Здесь нам на помощь придут делегаты. Вспомним, что
делегат представляет собой нечто похожее на
указатель на функцию в С++.
Для элемента управления result выполним следующие
действия:
result.Invoke(new MethodInvoker
(delegate {result.AppendText("Received: "+msg+"\n");}));
Теперь проблемы с перекрестным доступом между
потоками решены.
Пример чат-клиента
Что касается
отправки
сообщений, это
действие не
требует создания
отдельного
потока, и может
быть назначено в
качестве
обработчика
кнопки send.
Download