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

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

    В следующей программе создается двумерный числовой массив, и этот массив построчно заполняется. Заполнение каждой строки выполняется отдельным потоком. Объектные переменные, через которые реализуются ссылки на потоки, организованы в массив. Это общая идея. Далее рассмотрим способ ее реализации. Интересующий нас программный код представлен в тексте ниже.

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

using System.Threading;

namespace pr245_1
{
    class Program
    {
        // Главный метод: 
        static void Main()
        {
            // Двумерный массив: 
            int[,] nums = new int[6, 9];
            // Массив из объектных переменных для потоков:
            Thread[] t = new Thread[nums.GetLength(0)];
            // Перебор элементов массива: 
            for(int i = 0; i < t.Length; i++) {
                // Локальная переменная для цикла: 
                int p = i;
                // Создание объекта потока: 
                t[i] = new Thread(()=> {
                    // Перебор элементов в строке
                    // целочисленного массива: 
                    for(int j = 0; j < nums.GetLength(1); j++) {
                        // Элементу присваивается значение: 
                        nums[p, j] = (p + 1) * (j + 1);
                        // Приостановка выполнения потока:
                        Thread.Sleep(100);
                    }
                });
                // Запуск потока на выполнение: 
                t[i].Start();
            }
            
            // Ожидание завершения дочерних потоков: 
            for(int i = 0; i < t.Length; i++) { 
                if (t[i].IsAlive) t[i].Join();
            }

            // Отображение содержимого двумерного целочисленного 
            // массива:
            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. Результат работы приложения

    Мы создаем двумерный целочисленный массив nums. Еще мы создаем массив t из объектных переменных класса Thread. Размер массива t равен количеству строк в массиве nums (вычисляется инструкцией nums.GetLength(0)). После этого перебираются элементы массива t, и на каждой итерации цикла при фиксированном индексе i выполняются такие команды:

    Таким образом, после выполнения конструкции цикла будут запущены дочерние потоки. Количество дочерних потоков равно количеству строк в двумерном массиве. Ссылки на объекты потоков записаны в массив t. Каждый поток заполняет одну строку двумерного массива.


Несложно заметить, что переменная p фактически дублирует переменную i в конструкции цикла. Почему нельзя было вместо p использовать i? Переменная p для каждого цикла своя. Переменная i для всех циклов общая. Если бы в лямбда-выражении вместо переменной p использовалась переменная i, то методы, выполняемые в разных потоках, ссылались бы на одну и ту же переменную i, которая изменяет свое значение в процессе выполнения цикла. Выполнение кода становится непредсказуемым - точнее, предсказуемо заканчивается ошибкой.

    Далее по плану содержимое массива должно отображаться в консольном окне. Но для этого главный поток должен дождаться окончания выполнения дочерних потоков. Поэтому запускается конструкция цикла, в которой перебираются все элементы из массива t. На каждой итерации цикла для данного индекса i проверяется условие t[i].IsAlive. Если оно истинно (поток продолжает работу), то ожидается завершение потока (команда

  t[i].Join();
). После того как все дочерние потоки завершили работу (это означает, что двумерный массив заполнен), с помощью вложенных циклов отображается содержимое двумерного массива nums.


Инструкция {0,-4} в строке форматирования в методе WriteLine() означает, что под соответствующий аргумент при отображении в консольном окне отводится 4 позиции, а выравнивание выполняется по левому краю (поскольку второе число отрицательное).

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




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