Системне програмне забезпечення / Лабораторні / Semestr2 / Lab3
.docЛабораторна робота №3
Завдання: скласти програму в якій задати масив А з 12 елементів дійсних чисел, створити 4 дочірніх потоки і в кожному виконати певну арифметичну дію. Для першого потоку
S1=(A[і] - №(по списку)) де і змінюється від 1 до 12
Для другого
S2= (A[і] + №(по списку))
Для третього
S3= (A[і] * №(по списку))
Для 4 –го
S4= (A[і] / №(по списку))
Вивести всі результати в текстовий файл і після завершення дочірніх потоків в головному потоці підрахувати їх суму та вивести її та проміжні результати на екран.
Теоритичні відомості:
Для створення потоків використовується конструктор класу Thread, що приймає як параметр делегат типу ThreadStart, що вказує метод, якому потрібно виконати. Делегат ThreadStart визначається так:
public delegate void ThreadStart(); |
Виклик методу Start починає виконання потоку. Потік продовжується до виходу з методу, що виконується. От приклад, що використовує повний синтаксис C# для створення делегата ThreadStart:
ПРИМІТКА
Усі приклади припускають, що імпортуються наступні простори імен (якщо цей момент спеціально не обмовляється):
using System;
using System.Threading;
class ThreadTest { static void Main() { Thread t = new Thread(new ThreadStart(Go)); t.Start(); // Виконати Go() у новому потоці. Go(); // Одночасно запустити Go() у головному потоці. }
static void Go() { Console.WriteLine("hello!"); } |
У цьому прикладі потік виконує метод Go() одночасно з головним потоком. Результат – два майже одночасних «hello»:
hello! hello! |
Потік можна створити, використовуючи для присвоювання значень делегатам більш зручний скорочений синтаксис C#:
static void Main() { Thread t = new Thread(Go); // Без явного використання ThreadStart t.Start(); ... }
static void Go() { ... } |
У цьому випадку делегат ThreadStart виводиться компілятором автоматично. Інший варіант скороченого синтаксису використовує анонімний метод для створення потоку:
static void Main() { Thread t = new Thread(delegate() { Console.WriteLine("Hello!"); }); t.Start(); } |
Потік має властивість IsAlive, що повертає true після виклику Start() і до завершення потоку.
Потік, що закінчив виконання, не може бути початий заново.
Передача даних у ThreadStart
Допустимо, що в розглянутому вище прикладі ми захочемо більш явно розрізняти виведення кожного з потоків, наприклад, по регістрі символів. Можна домогтися цього, передаючи відповідний прапор у метод Go(), але в цьому випадку не можна використовувати делегат ThreadStart, так він не приймає аргументів. На щастя, .NET Framework визначає іншу версію делегата – ParameterizedThreadStart, що може приймати один аргумент:
public delegate void ParameterizedThreadStart(object obj); |
Попередній приклад можна переписати так:
class ThreadTest { static void Main() { Thread t = new Thread(Go); t.Start(true); // == Go(true) Go(false); }
static void Go(object upperCase) { bool upper = (bool)upperCase; Console.WriteLine(upper ? "HELLO!" : "hello!"); } } |
Консольний виведення:
hello! HELLO! |
У цьому прикладі компілятор автоматично виводить делегат ParameterizedThreadStart, тому що метод Go() приймає як параметр один object. З тим же успіхом можна було написати:
Thread t = new Thread(new ParameterizedThreadStart(Go)); t.Start(true); |
Особливість використання ParameterizedThreadStart полягає в тому, що перед використанням потрібно привести аргумент із типу object до потрібного типу (у даному випадку bool). До того ж існує только версія, що приймає єдиний аргумент.
Як альтернативу можна використовувати анонімний метод:
static void Main() { Thread t = new Thread(delegate(){ WriteText("Hello"); }); t.Start(); }
static void WriteText(string text) { Console.WriteLine(text); } |
Зручність полягає в тому, що потрібний метод (у даному випадку WriteText) можна викликати з будь-якою кількістю аргументів і без усякого приведення типів. Однак потрібно взяти до уваги особливість семантики анонімних методів, зв'язану з зовнішньої перемінної, котра стає очевидної в наступному прикладі:
static void Main() { string text = "Before"; Thread t = new Thread(delegate() { WriteText(text); }); text = "After"; t.Start(); }
static void WriteText(string text) { Console.WriteLine(text); } |
Консольне виведення:
-
After
Інший спосіб передачі даних у потік складається в запуску в потоці методу визначеного екземпляра об'єкта, а не статичного методу. Тоді властивості обраного екземпляра об'єкта будуть визначати поводження потоку, як у наступному варіанті оригінального приклада:
class ThreadTest { bool upper;
static void Main() { ThreadTest instance1 = new ThreadTest(); instance1.upper = true; Thread t = new Thread(instance1.Go); t.Start(); ThreadTest instance2 = new ThreadTest(); instance2.Go(); // Запуск у головному потоці - з upper=false }
void Go(){ Console.WriteLine(upper ? "HELLO!" : "hello!"); } |
Іменування потоків
Потік можна поименовать, використовуючи властивість Name. Це надає велику зручність при налагодженні: імена потоків можна вивести в Console.WriteLine і побачити у вікні Debug – Threads у Microsoft Visual Studio. Ім'я потокові може бути призначене в будь-який момент, але тільки один раз – при спробі змінити його буде сгенерировано виключення.
Головному потокові додатка також можна призначити ім'я – у наступному прикладі доступ до головного потоку здійснюється через статичну властивість CurrentThread класу Thread:
class ThreadNaming { static void Main() { Thread.CurrentThread.Name = "main"; Thread worker = new Thread(Go); worker.Name = "worker"; worker.Start(); Go(); }
static void Go() { Console.WriteLine("Hello from " + Thread.CurrentThread.Name); } } |
Консольний виведення:
Hello from main Hello from worker |