Файл: 1_C#_Введение_в_программирование_Уч_пособ_(Марченко-2005).pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.12.2025
Просмотров: 1396
Скачиваний: 3
Организация взаимодействия потоков
1. Посредством общедоступных (public) данных
В предлагаемом примере вторичные потоки взаимодействуют между собой через общедоступный объект, располагаемый в общей памяти потоков. Данные для обработки подготавливаются соответствующими конструкторами классов, отвечающих за выполнение вторичных потоков. Конструктор класса, выполняемого во вторичном потоке, также отвечает за организацию уведомления главного потока о результатах взаимосвязанной деятельности вторичных потоков.
using System;
using System.Threading;
namespace CommunicatingThreadsData
{
public delegate void CallBackFromStartClass (long param);
//Данные. Предмет и основа взаимодействия двух потоков. class CommonData
{
public long lVal;
public CommonData(long key)
{
lVal = key;
}
}
//Классы Worker и Inspector: основа взаимодействующих потоков. class Worker
{
CommonData cd;
//Конструктор...
public Worker(ref CommonData rCDKey)
{
cd = rCDKey;
}
public void startWorker()
{
DoIt(ref cd);
}
// Тело рабочей функции...
public void DoIt(ref CommonData cData) {//==================================== for (;;)
{
cData.lVal++; // Изменили значение… Console.Write(«{0,25}\r»,cData.lVal); // Сообщили о результатах.
}
}//====================================
}
class Inspector
{
long stopVal; CommonData cd;
CallBackFromStartClass callBack;
// Конструктор... Подготовка делегата для запуска CallBack метода.
public Inspector(ref CommonData rCDKey, long key, CallBackFromStartClass cbKey)
{
stopVal = key; cd = rCDKey; callBack = cbKey;
}
public void startInspector()
152
{
measureIt(ref cd);
}
// Тело рабочей функции...
public void measureIt(ref CommonData cData) {//==================================== for (;;)
{
if (cData.lVal < stopVal)
{
Thread.Sleep(100); Console.WriteLine(“\n{0,-25}”,cData.lVal);
}
else callBack(cData.lVal);
}
}//====================================
}
class StartClass
{
static Thread th0, th1; static CommonData cd; static long result = 0;
static void Main(string[] args)
{
StartClass.cd = new CommonData(0);
//Конструкторы классов Worker и Inspector несут дополнительную нагрузку.
//Они обеспечивают необходимыми значениями методы,
//выполняемые во вторичных потоках.
Worker work;
//До начала выполнения потока вся необходимая информация доступна методу. work = new Worker(ref cd);
Inspector insp;
//На инспектора возложена дополнительная обязанность вызова функции-терминатора.
//Для этого используется специально определяемый и настраиваемый делегат.
insp = new Inspector(ref cd, 50000,
new CallBackFromStartClass(StartClass.StopMain));
//Стартовые функции потоков должны соответствовать сигнатуре
//класса делегата ThreadStart. Поэтому они не имеют параметров. ThreadStart t0, t1;
t0 = new ThreadStart(work.startWorker);
t1 = new ThreadStart(insp.startInspector);
//Созданы вторичные потоки.
StartClass.th0 = new Thread(t0);
StartClass.th1 = new Thread(t1);
//Запущены вторичные потоки. StartClass.th0.Start(); StartClass.th1.Start();
//Ещё раз о методе Join(): Выполнение главного потока приостановлено. StartClass.th0.Join();
StartClass.th1.Join();
//Потому последнее слово остаётся за главным потоком приложения. Console.WriteLine(“Main(): All stoped at {0}. Bye.», result);
}
153
//Функция-член класса StartClass выполняется во ВТОРИЧНОМ потоке! public static void StopMain(long key)
{
Console.WriteLine(“StopMain: All stoped at {0}...”, key);
//Остановка рабочих потоков. Её выполняет функция-член
//класса StartClass. Этой функции в силу своего определения
//известно ВСЁ о вторичных потоках. Но выполняется она
//в ЧУЖОМ (вторичном) потоке. Поэтому:
//1. надо предпринять особые дополнительные усилия для того чтобы
//результат работы потоков оказался доступен в главном потоке. /*StartClass.*/result = key;
//2. очень важна последовательность остановки потоков, StartClass.th0.Abort();
StartClass.th1.Abort();
//Этот оператор не выполняется! Поток, в котором выполняется
//метод-член класса StartClass StopMain() остановлен. Console.WriteLine(«StopMain(): bye.»);
}
}
}
2. Посредством общедоступных (public) свойств
Следующий вариант организации взаимодействия между потоками основан на использовании общедоступных свойств. От предыдущего примера отличается тем, что доступ к закрытому счётчику lVal в соответствии с принципами инкапсуляции осуществляется через свойство с блоками get (акцессор) и set (мутатор).
using System;
using System.Threading;
namespace CommunicatingThreadsData
{
public delegate void CallBackFromStartClass (long param); // Данные. Предмет и основа взаимодействия двух потоков. class CommonData
{
private long lVal; public long lValProp
{
get
{
return lVal;
}
set
{
lVal = value;
}
}
public CommonData(long key)
{
lVal = key;
}
}
//Классы Worker и Inspector: основа взаимодействующих потоков. class Worker
{
CommonData cd;
//Конструктор умолчания...
public Worker(ref CommonData rCDKey)
{
cd = rCDKey;
}
public void startWorker()
{
154
DoIt(ref cd);
}
// Тело рабочей функции...
public void DoIt(ref CommonData cData) {//==================================== for (;;)
{
cData.lValProp++;
Console.Write(“{0,25}\r”,cData.lValProp);
}
}//====================================
}
class Inspector
{
long stopVal; CommonData cd;
CallBackFromStartClass callBack;
// Конструктор...
public Inspector(ref CommonData rCDKey, long key, CallBackFromStartClass cbKey)
{
stopVal = key; cd = rCDKey; callBack = cbKey;
}
public void startInspector()
{
measureIt(ref cd);
}
// Тело рабочей функции...
public void measureIt(ref CommonData cData) {//==================================== for (;;)
{
if (cData.lValProp < stopVal)
{
Thread.Sleep(100); Console.WriteLine(“\n{0,-25}”,cData.lValProp);
}
else callBack(cData.lValProp);
}
}//====================================
}
class StartClass
{
static Thread th0, th1; static CommonData cd; static long result = 0;
static void Main(string[] args)
{
StartClass.cd = new CommonData(0);
//Конструкторы классов Worker и Inspector несут дополнительную нагрузку.
//Они обеспечивают необходимыми значениями методы,
//выполняемые во вторичных потоках.
Worker work;
//До начала выполнения потока вся необходимая информация доступна методу. work = new Worker(ref cd);
Inspector insp;
//На инспектора возложена дополнительная обязанность вызова функции-терминатора.
//Для этого используется специально определяемый и настраиваемый делегат.
insp = new Inspector(ref cd, 50000, new CallBackFromStartClass(StartClass.StopMain));
155
//Стартовые функции потоков должны соответствовать сигнатуре
//класса делегата ThreadStart. Поэтому они не имеют параметров. ThreadStart t0, t1;
t0 = new ThreadStart(work.startWorker);
t1 = new ThreadStart(insp.startInspector);
//Созданы вторичные потоки. StartClass.th0 = new Thread(t0); StartClass.th1 = new Thread(t1);
//Запущены вторичные потоки. StartClass.th0.Start(); StartClass.th1.Start();
//Ещё раз о методе Join(): Выполнение главного потока приостановлено. StartClass.th0.Join();
StartClass.th1.Join();
//Потому последнее слово остаётся за главным потоком приложения. Console.WriteLine(“Main(): All stoped at {0}. Bye.», result);
}
//Функция-член класса StartClass выполняется во ВТОРИЧНОМ потоке! public static void StopMain(long key)
{
Console.WriteLine(“StopMain: All stoped at {0}...”, key);
//Остановка рабочих потоков. Её выполняет функция-член
//класса StartClass. Этой функции в силу своего определения
//известно ВСЁ о вторичных потоках. Но выполняется она
//в ЧУЖОМ (вторичном) потоке. Поэтому:
//1. надо предпринять особые дополнительные усилия для того чтобы
//результат работы потоков оказался доступен в главном потоке. /*StartClass.*/result = key;
//2. очень важна последовательность остановки потоков, StartClass.th0.Abort();
StartClass.th1.Abort();
//Этот оператор не выполняется! Поток, в котором выполняется
//метод-член класса StartClass StopMain() остановлен.
Console.WriteLine(“StopMain(): bye.”);
}
}
}
3. Посредством общедоступных очередей
Взаимодействующие потоки выполняют поставленную перед ними задачу. При этом один поток обеспечивает генерацию данных, а второй – обработку получаемых данных. Если при этом время, необходимое для генерации данных и время обработки данных различаются, проблема взаимодействия потоков может быть решена посредством очереди данных, которая в этом случае играет роль буфера между двумя потоками. Первый поток размещает данные в очередь “с одного конца”, абсолютно не интересуясь успехами потока-обработчика. Второй поток извлекает данные из очереди “с другого конца” руководствуясь исключительно состоянием этой очереди. Отсутствие данных в очереди для потока-обработчика означает успешное выполнение поставленной задачи или сигнал к самоусыплению.
Организация работы потоков по этой схеме предполагает:
выделение обрабатываемых данных в отдельный класс,
создание общедоступного объекта-представителя класса “Очередь” с интерфейсом, обеспечивающим размещение и извлечение данных,
разработку классов, содержащих методы генерации и обработки данных, реализующих интерфейс доступа к данным,
разработку методов обратного вызова, сообщающих о результатах выполнения задач генерации и обработки данных,
создание и запуск потока, обеспечивающего генерацию данных и размещение данных в очереди,
156
создание и запуск потока, обеспечивающего извлечение данных из очереди и обработку данных.
Пример
using System;
using System.Threading;
using System.Collections; namespace CommunicatingThreadsQueue
{
public delegate void CallBackFromStartClass (string param); // Данные. Предмет и основа взаимодействия двух потоков. class CommonData
{
private int iVal; public int iValProp
{
get
{
return iVal;
}
set
{
iVal = value;
}
}
public CommonData(int key)
{
iVal = key;
}
}
//Классы Receiver и Sender: основа взаимодействующих потоков. class Receiver
{
Queue cdQueue; CallBackFromStartClass callBack;
//Конструктор умолчания...
public Receiver(ref Queue queueKey, CallBackFromStartClass cbKey)
{
cdQueue = queueKey; callBack = cbKey;
}
public void startReceiver()
{
DoIt();
}
// Тело рабочей функции...
public void DoIt() {//==================================== CommonData cd = null;
while (true)
{
Console.WriteLine(“Receiver. notifications in queue: {0}”,cdQueue.Count); if (cdQueue.Count > 0)
{
cd = (CommonData)cdQueue.Dequeue(); if (cd == null) Console.WriteLine(“?????”);
else
{
Console.WriteLine(“Process started ({0}).”, cd.iValProp);
157
//Выбрать какой-нибудь из способов обработки полученного уведомления.
//Заснуть на соответствующее количество тиков. //Thread.Sleep(cd.iValProp);
//Заняться элементарной арифметикой. С усыплением потока.
while (cd.iValProp != 0)
{
cd.iValProp--; Thread.Sleep(cd.iValProp);
Console.WriteLine(“process:{0}”,cd.iValProp);
}
}
}
else callBack(“Receiver”);
Thread.Sleep(100);
}
}//====================================
}
class Sender
{
Random rnd; int stopVal; Queue cdQueue;
CallBackFromStartClass callBack;
// Конструктор...
public Sender(ref Queue queueKey, int key, CallBackFromStartClass cbKey)
{
rnd = new Random(key); stopVal = key; cdQueue = queueKey; callBack = cbKey;
}
public void startSender()
{
sendIt();
}
// Тело рабочей функции...
public void sendIt() {//====================================
while (true)
{
if (stopVal > 0)
{
// Размещение в очереди нового члена со случайными характеристиками. cdQueue.Enqueue(new CommonData(rnd.Next(0,stopVal)));
stopVal--;
}
else callBack(“Sender”);
Console.WriteLine(“Sender. in queue:{0}, the rest of notifications:{1}.”, cdQueue.Count, stopVal);
Thread.Sleep(100);
}
}//====================================
}
class StartClass
{
static Thread th0, th1;
static Queue NotificationQueue;
static string[] report = new string[2];
158
static void Main(string[] args)
{
StartClass.NotificationQueue = new Queue();
//Конструкторы классов Receiver и Sender несут дополнительную нагрузку.
//Они обеспечивают необходимыми значениями методы,
//выполняемые во вторичных потоках.
Sender sender;
//По окончании работы отправитель вызывает функцию-терминатор.
//Для этого используется специально определяемый и настраиваемый делегат. sender = new Sender(ref NotificationQueue,
100,
new CallBackFromStartClass(StartClass.StopMain));
Receiver receiver;
//Выбрав всю очередь получатель вызывает функцию-терминатор. receiver = new Receiver(ref NotificationQueue,
new CallBackFromStartClass(StartClass.StopMain));
//Стартовые функции потоков должны соответствовать сигнатуре
//класса делегата ThreadStart. Поэтому они не имеют параметров. ThreadStart t0, t1;
t0 = new ThreadStart(sender.startSender);
t1 = new ThreadStart(receiver.startReceiver);
//Созданы вторичные потоки.
StartClass.th0 = new Thread(t0);
StartClass.th1 = new Thread(t1);
//Запущены вторичные потоки. StartClass.th0.Start(); StartClass.th1.Start();
//Ещё раз о методе Join():
//Выполнение главного потока приостановлено до завершения
//выполнения вторичных потоков. StartClass.th0.Join(); StartClass.th1.Join();
//Потому последнее слово остаётся за главным потоком приложения. Console.WriteLine(“Main(): “ + report[0] + “...” + report[1] + “... Bye.”);
}
//Функция-член класса StartClass выполняется во ВТОРИЧНОМ потоке! public static void StopMain(string param)
{
Console.WriteLine(“StopMain: ” + param);
//Остановка рабочих потоков. Её выполняет функция-член
//класса StartClass. Этой функции в силу своего определения
//известно ВСЁ о вторичных потоках. Но выполняется она
//в ЧУЖИХ (вторичных) потоках.
if (param.Equals(“Sender”))
{ report[0] = “Sender all did.”; StartClass.th0.Abort();
}
if (param.Equals(“Receiver”))
{ report[1] = “Receiver all did.”; StartClass.th1.Abort();
}
//Этот оператор не выполняется! Поток, в котором выполняется
//метод-член класса StartClass StopMain() остановлен. Console.WriteLine(“StopMain(): bye.”);
}
}
}
Состязание потоков
В ранее рассмотренном примере временные задержки при генерации и обработке данных подобраны таким образом, что обработчик завершает свою деятельность
159