Шаг 242.
Язык программирования C#. Начала.
Многопоточное программирование. Операции с потоками

    На этом шаге мы рассмотрим эти операции и приведем пример их использования.

    Существуют свойства и методы, которые позволяют выполнять различные операции с потоками. Например, у класса Thread есть статическое свойство CurrentThread. Значением этого свойства является ссылка на объект потока, в котором запрашивается свойство.


Например, мы имели дело с главным потоком, но никогда ранее не упоминали объект главного потока. Меж тем он существует, и к нему можно получить доступ. Для этого в главном потоке следует воспользоваться свойством CurrentThread класса Thread.

    У объекта потока есть свойство Name, определяющее название потока. Свойство Priority объекта потока определяет приоритет потока. Возможные значения свойства - константы Highest, AboveNormal, Normal (значение по умолчанию), BelowNormal и Lowest из перечисления ThreadPriority.


Приоритет потока влияет на доступ потока к системным ресурсам, особенно если несколько потоков конкурируют за один и тот же ресурс. Обычно определяющим является не само значение приоритета, а то, у какого из потоков приоритет выше.

    С помощью метода Abort() можно завершить выполнение потока, из объекта которого вызывается метод. Небольшой пример, в котором используются перечисленные выше свойства, представлен в тексте ниже.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;

namespace pr242_1
{
    // Главный класс:
    class Program
    {
        // Статическое поле: 
        static int state = 0;
        // Статический метод: 
        static void run(bool type) {
            // Ссылка на объект потока,
            // в котором вызывается метод:
            Thread t = Thread.CurrentThread;
            // Отображение сообщения с названием потока: 
            Console.WriteLine("Поток " + t.Name + " запущен...");
            // Бесконечный цикл: 
            while(true) {
                // Изменение значения статического поля: 
                if (type) state++;
                else state--;
                // Поток приостанавливает выполнение: 
                Thread.Sleep(1000);
            }
        }

        // Главный метод: 
        static void Main()
        {
            // Получение ссылки на главный поток:
            Thread t = Thread.CurrentThread;
            // Название потока: 
            t.Name = "Base";
            // Отображение сообщения с названием потока: 
            Console.WriteLine("Главный поток {0} запущен...", t.Name); 
            // Отображение значения статического поля: 
            Console.WriteLine("Haчaльнoe значение: {0}", state);
            // Создание объекта для первого дочернего потока:
            Thread up = new Thread(() => run(true));
            // Создание объекта для второго дочернего потока:
            Thread down = new Thread(() => run(false));
            // Название для первого потока: 
            up.Name = "Alpha";
            // Приоритет для первого потока: 
            up.Priority = ThreadPriority.Highest;
            // Название для второго потока: 
            down.Name = "Bravo";
            // Приоритет для второго потока: 
            down.Priority = ThreadPriority.Lowest;
            // Запуск первого потока: 
            up.Start();
            // Запуск второго потока: 
            down.Start();
            // Приостановлено выполнение главного потока:
            Thread.Sleep(5000);
            // Завершение выполнения первого потока: 
            up.Abort();
            // Завершение выполнения второго потока: 
            down.Abort();
            // Итоговое значение статического поля:
            Console.WriteLine("Итоговое значение: {0}", state);
            // Сообщение о завершении главного потока:
            Console.WriteLine("Главный поток {0} завершен...", t.Name);
        }
    }
}
Архив проекта можно взять здесь.

    Результат выполнения программы может быть таким:


Рис.1. Результат работы приложения

    В главном классе объявляется статическое целочисленное поле state с начальным нулевым значением. Еще у нас есть статический метод run() с аргументом логического типа (обозначен как type). В теле метода командой

  Thread t = Thread.CurrentThread;
объявляется объектная переменная t класса Thread, а в качестве значения переменной присваивается ссылка на объект потока, в котором выполняется метод. Ссылку на объект потока мы получаем с помощью статического свойства CurrentThread класса Thread. После этого появляется сообщение с названием потока, в котором выполняется метод. Название потока получаем с помощью инструкции t.Name. При этом мы приняли во внимание, что переменная t ссылается на объект потока. После отображения сообщения запускается цикл while, в котором условием указано значение true. Поэтому формально получаем бесконечный цикл (конструкция цикла, которая не останавливается). Если метод run() вызван с аргументом true, то за каждую итерацию цикла значение поля state увеличивается на единицу. Если метод run() вызван с аргументом false, то за каждую итерацию значение поля state уменьшается на единицу. После каждого изменения значения поля state выполняется пауза в 1 секунду. Для этого используется команда
  Thread.Sleep(1000);               .

    В главном методе программы командой

  Thread t = Thread.CurrentThread;
в переменную t записывается ссылка на главный поток программы.


В методе run() была аналогичная команда. Но, во-первых, переменная t в методе run() и переменная t в главном методе - это совершенно разные переменные. Во-вторых, значением инструкции Thread.CurrentThread является ссылка на объект потока, в котором эта инструкция выполняется. Поэтому в методе run() в переменную t записывается ссылка на объект потока, в котором выполняется метод run(), а в главном методе в переменную t записывается ссылка на объект главного потока.

    Командой

  t.Name = "Base";
главному потоку присваивается название "Base". Отображается сообщение о запуске главного потока и начальное значение поля state. Дочерние потоки (объекты) создаются командами
  Thread up = new Thread(() => run(true));
и
  Thread down = new Thread(() => run(false));    . 
В этих командах методы, выполняемые в потоках, определяются с помощью лямбда-выражений. В итоге в методе up будет выполняться метод run() с аргументом true (поток увеличивает значение поля state), а в потоке down будет выполняться метод run() с аргументом false (поток уменьшает значение поля state). Перед запуском для потоков задаются названия (команды
  up.Name = "Alpha";
и
  down.Name = "Bravo";
) и приоритет (команды
  up.Priority = ThreadPriority.Highest;
и
  down.Priority = ThreadPriority.Lowest;
). Для запуска потоков на выполнение используем команды
  up.Start();
и
  down.Start();     .


Откровенно говоря, в данном случае приоритет потоков мало на что влияет. Команды, определяющие приоритет потоков, приведены исключительно как иллюстрация к способу использования соответствующих свойств объекта потока, не более. Мы останавливаем выполнение дочерних потоков из главного потока.

    Командой

  Thread.Sleep(5000); 
выполняется приостановка выполнения главного потока на 5 секунд, после чего командами
  up.Abort();
и
  down.Abort(); 
завершается выполнение дочерних потоков.


Напомним, что метод run() содержит бесконечный цикл, а созданные потоки не являются фоновыми, поэтому они должны были бы выполняться бесконечно долго.

    После завершения работы дочерних потоков проверяется значение поля state. От запуска к запуску это значение может меняться, но обычно оно не сильно отличается от начального значения 0 (поскольку каждый из дочерних потоков успевает выполнить примерно одинаковое количество циклов по изменению значения поля). Завершается выполнение главного потока отображением сообщения соответствующего содержания.

    На следующем шаге мы рассмотрим синхронизацию потоков.




Предыдущий шаг Содержание Следующий шаг