Многопоточность, Thread
Иногда в реализации нашего кода может появиться момент, когда мы хотим, чтобы два действия выполнялись одновременно. Однако с самого начала обучения мы с вами знаем, что код идет сверху вниз и все. Как же нам разделить этот код на две отдельные последовательности, которые будут идти одновременно?
Для этого существуют потоки. В 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();
Переменная ругается, потому что внутри нее нужно указать какой-то код, который будет выполняться в этом отдельном потоке. Сделать это можно двумя способами
- Вынести код с моей задачей – отрисовку строчки – в отдельный метод. Внутри 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); } }
- С помощью лямбда выражения (вспоминаем сложные 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 милисекунд