Многопоточность

Многопоточность в C#: класс Thread, Sleep и параллельное выполнение

Иногда в реализации нашего кода может появиться момент, когда мы хотим, чтобы два действия выполнялись одновременно. Однако с самого начала обучения мы с вами знаем, что код идет сверху вниз и все. Как же нам разделить этот код на две отдельные последовательности, которые будут идти одновременно?

Для этого существуют потоки. В C# поток – Thread. Когда потоков много, у нас реализовывается многопоточность

Дисклеймер: здесь представлена очень поверхностная работа с потоками, чисто в рамках выполнения двух задач параллельно. Более сложные темы будут рассмотрены позже, а тема о многопоточности будет расширяться

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

Изначально, в нашем коде есть один поток – основной. Он запускается вместе с программой, и именно в этом потоке у нас воспроизводится весь наш код. Но что, если у меня есть что-то, что блокирует выполнение этого кода, например, бесконечный цикл?

Например, я хочу, чтобы у меня каждую секунду мелькала строчка то на нулевой строке, то на второй строке. Я напишу вот такой код

while (true)
{
Console.SetCursorPosition(0, 2);
Console.WriteLine(" ");
Console.SetCursorPosition(0, 0);
Console.WriteLine("---");
Thread.Sleep(1000);
Console.SetCursorPosition(0, 2);
Console.WriteLine("---");
Console.SetCursorPosition(0, 0);
Console.WriteLine(" ");
Thread.Sleep(1000);
}

Уже здесь мы видим работу с потоками. Мы останавливаем основной поток с помощью команды Thread.Sleep(); на 1000 милисекунд, т.е. на 1 секунду.

Параллельно с этим я хочу, чтобы между этими строчками у меня было постоянное чтение символа с консоли и его вывод. Для этого, чуть ниже, я напишу код для постоянного чтения символа с консоли. Итоговый код будет выглядеть вот так

while (true)
{
Console.SetCursorPosition(0, 2);
Console.WriteLine(" ");
Console.SetCursorPosition(0, 0);
Console.WriteLine("---");
Thread.Sleep(1000);
Console.SetCursorPosition(0, 2);
Console.WriteLine("---");
Console.SetCursorPosition(0, 0);
Console.WriteLine(" ");
Thread.Sleep(1000);
}
while (true)
{
ConsoleKeyInfo key = Console.ReadKey();
Console.SetCursorPosition(0, 1);
Console.Write(" ");
Console.SetCursorPosition(0, 1);
Console.Write(key.Key);
}

Однако здесь мы видим, что у нас два бесконечных цикла. Код и из одного не выйдет, а разговора о втором даже не заходит. Однако я очень хочу, чтобы эти два кода работали одновременно. Тогда, один из этих циклов, мне нужно вынести в отдельный поток – Thread

Выносить я буду, например, отрисовку строчки. Чтобы понять, что нужно выносить - лучше выносите что-то фоновое, постоянное. В моем случае, отрисовка полоски - постоянное действие, и выношу я ее.

Thread – сложный тип данных, так что, чтобы создать поток, нам нужно создать переменную типа данных Thread

Thread thread = new Thread();

Переменная ругается, потому что внутри нее нужно указать какой-то код, который будет выполняться в этом отдельном потоке. Сделать это можно двумя способами

  1. Вынести код с моей задачей – отрисовку строчки – в отдельный метод. Внутри Thread написать new ThreadStart(НазваниеМетода). ThreadStart говорит нам о том, какой метод надо запустить при старте этого потока

    Обратите внимание, что название метода в этом случае будет без круглых скобок! Еще обратите внимание, что если у нас внутри метода есть параметры, то такой способ использовать нельз

    Thread thread = new Thread(new ThreadStart(Method));
    void Method()
    {
    while (true)
    {
    Console.SetCursorPosition(0, 2);
    Console.WriteLine(" ");
    Console.SetCursorPosition(0, 0);
    Console.WriteLine("---");
    Thread.Sleep(1000);
    Console.SetCursorPosition(0, 2);
    Console.WriteLine("---");
    Console.SetCursorPosition(0, 0);
    Console.WriteLine(" ");
    Thread.Sleep(1000);
    }
    }
  2. С помощью лямбда выражения (вспоминаем сложные linq запросы, лямбда выражение это где =>), сразу вписать код, который мы хотим выполнить при старте. Наша лямбда – выражение будет выгляжить как _ => , где _ - переменная заглушка, а внутри фигурных скобок будет код, который должен выполняться в потоке. Внутри этих фигурных скобок можно также вызывать методы и передавать туда значения в параметры

    Thread thread = new Thread(_ =>
    {
    while (true)
    {
    Console.SetCursorPosition(0, 2);
    Console.WriteLine(" ");
    Console.SetCursorPosition(0, 0);
    Console.WriteLine("---");
    Thread.Sleep(1000);
    Console.SetCursorPosition(0, 2);
    Console.WriteLine("---");
    Console.SetCursorPosition(0, 0);
    Console.WriteLine(" ");
    Thread.Sleep(1000);
    }
    });

После того, как мы определили, какой код будет выполняться в другом потоке, мы должны его запустить. Чтобы запустить поток, надо написать названиепеременной.Start();. И тогда, наш код будет параллельно выполняться в двух потоках

Суммарно, код, с вторым вариантом потока, выглядит вот так

Thread thread = new Thread(_ =>
{
while (true)
{
Console.SetCursorPosition(0, 2);
Console.WriteLine(" ");
Console.SetCursorPosition(0, 0);
Console.WriteLine("---");
Thread.Sleep(1000);
Console.SetCursorPosition(0, 2);
Console.WriteLine("---");
Console.SetCursorPosition(0, 0);
Console.WriteLine(" ");
Thread.Sleep(1000);
}
});
thread.Start();
while (true)
{
ConsoleKeyInfo key = Console.ReadKey();
Console.SetCursorPosition(0, 1);
Console.Write(" ");
Console.SetCursorPosition(0, 1);
Console.Write(key.Key);
}

А при запуске, мы увидим, что мы можем и символы вводить, и полосочки отрисовываются. Причем одновременно!

Результат работы

Дополнительно, мы можем не только запускать код, но и узнать информацию о нашем потоке. Например:

  • Узнать текущий поток – Thread.CurrentThread
  • Выставить приоритет выполнения текущего потока - Thread.CurrentThread.Priority
  • Остановить выполнение текущего потока - Thread.CurrentThread.Abort
  • Узнать, запущен ли поток - Thread.CurrentThread.IsAlive
  • Узнать или изменить состояние текущего потока - Thread.CurrentThread.CurrentState
  • Остановить поток на какой-то промежуток времени – Thread.Sleep(100). 100 здесь – 100 милисекунд
просмотров