Шаг 246.
Язык программирования C#. Начала.
Многопоточное программирование. Использование потоков (еще продолжение)

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

    В программе из примера ниже также заполняется двумерный целочисленный массив. Но на этот раз для заполнения массива используется специальный метод. При запуске метода ему передается ссылка на массив (который следует заполнить), индекс строки (начиная с которой заполняется массив) и ссылка на объект для генерирования случайных чисел. Если указанная для заполнения строка массива не является последней, то метод запускает дочерний поток, в котором вызывается этот же метод, но заполнять он должен следующую строку. Таким образом, в результате вызова метода построчно заполняется двумерный массив: начиная с той строки, которая указана при вызове метода, и до последней строки массива включительно. Каждая строка заполняется отдельным потоком.

    Теперь рассмотрим программный код, представленный ниже.

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

using System.Threading;

namespace pr246_1
{
    class Program
    {
        // Статический метод для заполнения двумерного массива 
        // случайными числами:
        static void fill(int[,] a, int k, Random rnd) {
            // Объектная переменная для потока:
            Thread t = null;
            // Если не последняя строка: 
            if (k < a.GetLength(0) - 1) {
                // Создание объекта потока: 
                t = new Thread(()=>{
                    // Вызов в потоке метода: 
                    fill(a, k + 1, rnd);
                });
                // Запуск потока на выполнение: 
                t.Start();
            }
            // Заполнение строки в массиве: 
            for (int m = 0; m < a.GetLength(1); m++) {
                // Значение элемента - случайное число: 
                a[k, m] = k * 10 + rnd.Next(10);
            }
            // Если объект потока существует и поток еще 
            // выполняется, то необходимо дождаться 
            // завершения потока: 
            if (t != null && t.IsAlive) t.Join();
        }

        // Главный метод:
        static void Main()
        {
            // Создается двумерный массив: 
            int[,] nums = new int[6, 9];
            // Объект для генерации случайных чисел:
            Random rnd = new Random();
            // Заполнение массива: 
            fill(nums, 0, rnd);
            // Отображение содержимого массива: 
            for (int i = 0; i < nums.GetLength(0); i++)
            {
                for (int j = 0; j < nums.GetLength(1); j++)
                {
                    Console.Write("{0,-4}", nums[i, j]);
                }
                Console.WriteLine();
            }
        }
    }
}
Архив проекта можно взять здесь.

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


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

    В главном классе программы, кроме метода Main(), также описывается статический метод fill() с тремя аргументами:

В теле метода объявляется объектная переменная t класса Thread.

    Ее начальное значение равно null (пустая ссылка). В условной конструкции проверяется условие k<a.GetLength(0)-1, истинное в случае, если обозначенная для заполнения строка не является последней в двумерном массиве. Если так, то создается объект потока и ссылка на этот объект записывается в переменную t. Поток определен таким образом, что при его выполнении вызывается метод fill() (команда

  fill(a, k + 1, rnd);
), но на этот раз его второй аргумент (индекс строки для заполнения) на единицу больше. После этого поток запускается на выполнение (команда
  t.Start();
) и выполняется заполнение строки случайными числами. Для этого использована конструкция цикла, в которой индексная переменная m определяет второй индекс элемента массива (первый индекс элемента массива - это индекс k отмеченной для заполнения строки). Значение элементу присваивается командой
  a[k, m] = k * 10 + rnd.Next(10);     . 
Поэтому первая строка (индекс k равен 0) заполняется случайными числами от 0 до 9, вторая строка (индекс k равен 1) заполняется случайными числами от 10 до 19, третья строка (индекс k равен 2) заполняется случайными числами от 20 до 29, и так далее.

    После заполнения строки числами в условной конструкции проверяется условие t != null && t.IsAlive. Если оно истинно, то вследствие команды

  t.Join();
метод ожидает завершения выполнения потока, после чего работа метода завершается.


В условной конструкции проверяется условие t != null && t.IsAlive. Состоит оно в том, что переменная t содержит непустую ссылку (попросту это означает, что дочерний поток был создан и запущен), и при этом поток, на объект которого ссылается переменная t, еще выполняется. Важно то, что использован оператор логического и &&, работающий по упрощенной схеме: если первое условие t!=null ложно, то второе условие t.IsAlive проверяться не будет. Проверка условия t.IsAlive при ложном условии t!=null приводит к ошибке.

    В главном методе программы создается двумерный массив nums и объект rnd класса Random для генерации случайных чисел. Массив заполняется при выполнении команды

  fill(nums, 0, rnd);          , 
после чего мы проверяем содержимое массива с помощью вложенных конструкций цикла.


Метод fill() ожидает завершения потока, если такой запускался при вызове метода. Поэтому завершение выполнения метода fill() означает, что запущенный методом поток также завершен. В дочернем потоке вызывается метод fill(), который может запустить еще один поток, и так далее. Но в силу того же обстоятельства (метод fill() ожидает завершения дочернего потока, запущенного методом) получается, что каждый дочерний поток ожидает завершения запущенного из него дочернего потока.

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




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