Файл: 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