Многопоточность в C#. Делегаты потоков

Во введении к циклу статей про Многопоточность в C# мы создали простое приложение, демонстрирующее работу двух потоков, не вдаваясь особенно в то, что мы написали. Сегодня мы более подробнее посмотрим на конструкторы класса Thread, а именно — рассмотрим делегаты, используемые при создании потоков.

Делегат ThreadStart

Итак, в предыдущей части мы использовали вот такой конструктор создания потока:

Thread thread = new Thread(new ThreadStart(Proc));

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

public delegate void ThreadStart();

Например, мы можем написать вот такое приложение, используя для потока делегат ThreadStart:

Thread thread = new Thread(new ThreadStart(Proc));
thread.Start();
for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"Значение из главного потока: {i * i * i}");
    Thread.Sleep(500);
}

static void Proc()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine($"Значение из второго потока: {i * i}");
        Thread.Sleep(1000);
    }
}

Второй поток будет выполнять метод Proc, который мы передали в качестве параметра делегату ThreadStart. Так как вторичный поток не определен как фоновый (свойство IsBackground по умолчанию равно false), то в консоли мы увидим примерно следующие результаты:

Значение из главного потока: 0
Значение из второго потока: 0
Значение из главного потока: 1
Значение из второго потока: 1
Значение из главного потока: 8
Значение из главного потока: 27
Значение из второго потока: 4
Значение из главного потока: 64
Значение из главного потока: 125
Значение из второго потока: 9
Значение из главного потока: 216
Значение из главного потока: 343
Значение из второго потока: 16
Значение из главного потока: 512
Значение из главного потока: 729
Значение из второго потока: 25
Значение из второго потока: 36
Значение из второго потока: 49
Значение из второго потока: 64
Значение из второго потока: 81

Делегат ParameterizedThreadStart

Если нам необходимо передать какие-либо параметры в поток, например, начальные значения переменных, то мы можем воспользоваться вторым делегатом — ParameterizedThreadStart. Выглядит этот делегат следующим образом:

public delegate void ParameterizedThreadStart(object? obj);

то есть, метод, передаваемый в параметре делегата не должен ничего возвращать, но может принимать один параметр типа object. А так как мы помним, что все в C# так или иначе является объектами, то подобный подход позволяет нам передать в поток вообще всё, что угодно — от простого числа до сложный объектов. В качестве демонстрации использования этого делегата, воспользуемся алгоритмом поиска простых чисел и напишем приложение в котором вторичный поток будет определять простые числа, а главный — выполнять ИБД (Имитацию Бурной Деятельности):

int N = 10000;

Thread thread = new Thread(new ParameterizedThreadStart(Proc));

thread.Start(N);//передаем начальные настройки потока

for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"Имитируем бурную деятельность главного потока");
    Thread.Sleep(1000);
}

static bool IsPrime(int number)
{
    for (int i = 2; i < number; i++)
    {
        if (number % i == 0)
            return false;
    }
    return true;
}
static void Proc(object N)
{
    int n = (int)N;
    for (int i = 1; i <= n; i++)
    {
        if (IsPrime(i))
        {
            Console.WriteLine($"{i} - простое число");
            Thread.Sleep(50);
        }
    }
}

Здесь стоит обратить внимание на то, как мы создаем и запускаем вторичный поток. В качестве параметра в конструкторе Thread мы используем делегат ParameterizedThreadStart в который передаем метод Proc. Метод Proc, как и требуется, принимает всего один параметр типа object, поэтому мы приводим параметр N к нужному нам типу — int. Чтобы указать конкретное значение, которое мы хотим передать в поток, мы используем перегруженную версию метода Start и в его параметре передаем необходимое значение в поток (в нашем случае — это значение 10000).

Конечно, такая работа с потоками не безопасна, как минимум по причине того, что мы могли бы передать в метод Start не число, а например, какой-то сложный объект или строку и тогда мы бы получили исключение. Избежать этого мы можем относительно просто — объявить специальный класс с необходимым нам методом без параметров и воспользоваться делегатом ThreadStart. Перепишем наше приложение следующим образом:

PrimeCounter primeCounter = new PrimeCounter();
primeCounter.N = 10000;
Thread thread = new Thread(new ThreadStart(primeCounter.Calculate));
thread.Start();
for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"Имирируем бурную деятельность главного потока");
    Thread.Sleep(1000);
}

public class PrimeCounter
{
    public int N { get; set; }
    private bool IsPrime(int number)
    {
        for (int i = 2; i < number; i++)
        {
            if (number % i == 0)
                return false;
        }
        return true;
    }
    public void Calculate()
    {
        for (int i = 1; i <= N; i++)
        {
            if (IsPrime(i))
            {
                Console.WriteLine($"{i} - простое число");
                Thread.Sleep(50);
            }
        }
    }
}

Обратите внимание на то, что все необходимые методы, в том числе и метод IsPrime вынесены в отдельный класс. Теперь наш код стал типобезопасным — мы уже не сможем передать классу в качестве значения свойства N строку. В потоке же мы воспользовались делегатом ThreadStart и передали ему метод Calculate класса PrimeCounter. При этом, результат работы программы никак не изменился.

Итого

Мы рассмотрели два делегата, используемых при создании потоков в C# — ThreadStart и ParameterizedThreadStart . Использование делегата ParameterizedThreadStart хоть и позволяет передавать в поток какую-либо информацию, однако использование этого делегата может быть сопряжено с проблемами безопасности, так как в поток может передаваться любое значение типа object. Более безопасно, с этой точки зрения, оформить все необходимые для потока операции в виде отдельного класса, в котором определить метод без параметров и передать его в качестве параметра для делегата ThreadStart в конструкторе потока Thread.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии