
Лабораторна робота №2
Теоретичні відомості:
Створення і запуск потоків
Для створення потоків використовується конструктор класу Thread, що приймає як параметр делегат типу ThreadStart, що вказує метод, якому потрібно виконати. Делегат ThreadStart визначається так:
public delegate void ThreadStart(); |
Виклик методу Start починає виконання потоку. Потік продовжується до виходу з методу, що виконується. От приклад, що використовує повний синтаксис C# для створення делегата ThreadStart:
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!"); } |