Потоки в C# .NET первые шаги

Уважаемые читатели, в этой статье я хочу рассказать о таком важном средстве многозадачного программирования среды .NET, как многопоточность. Данная статья содержит начальные сведения, и предназначена для быстрого освоения азов многопоточности на языке C#. Однако не буду разглагольствовать о преимуществах параллельного выполнения задач, и перейду к примеру кода.


using System;
using System.Threading; //Именно это пространство имен поддерживает многопоточность

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread myThread = new Thread(func); //Создаем новый объект потока (Thread)

            myThread.Start(); //запускаем поток

            for (int i = 0; i < 10; i++ )
            {
                Console.WriteLine("Поток 1 выводит " + i);
                Thread.Sleep(0);
            }

            Console.Read(); //Приостановим основной поток

        }

        //Функция запускаемая из другого потока
        static void func() 
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Поток 2 выводит " + i.ToString());
                Thread.Sleep(0);
            }

На что стоит обратить внимание в этой программе. В первую очередь это пространство имен System.Threading. Это пространство имен содержит в себе классы поддерживающие многопоточное программирование. И именно там содержится класс Thread, который мы используем далее в коде.
Далее мы создаем объект потока.
Thread myThread = new Thread(func);

Конструктору этого класса необходимо передать имя функции возвращающей void, которая собственно и будет вызываться в параллельном потоке. После чего мы запускаем наш поток методом Start() определенном внутри вновь созданного потока.

Итак, главный и второстепенный поток параллельно выполняют почти идентичный код они считают до 9 (как повелось, с нуля), перед этим назвав собственный номер. Обратите внимание на статический метод Thread.Sleep(0). Он приостанавливает поток вызвавший его на количество миллисекунд указанных в параметре. Но в данном случае ему передан параметр 0. Это означает что поток должен приостановиться для того чтобы дать возможность выполнения другому потоку.

И для того чтобы консоль не закрылась раньше времени, мы будем ожидать ввода с клавиатуры (Console.Read).В результате ваша программа выведет в консоль примерно следующее:
Поток 1 выводит 0
Поток 2 выводит 0
Поток 1 выводит 1
Поток 1 выводит 2
Поток 1 выводит 3
Поток 2 выводит 1
<…> и т.д.

Хотя результат вывода каждый раз будет разным. Это зависит от множества факторов.Стоит принять во внимание, что потоки бывают двух видов: приоритетные и фоновые. Фоновые потоки автоматически завершаются при завершении приоритетных. По умолчанию в приоритетном потоке запускается функция Main а остальные потоки создаются фоновыми. Именно поэтому мы должны следить за тем, чтобы главный поток не завершился до окончания производных. Для того чтобы не допустить этого мы можем использовать поле IsAlive, которое возвращает true если поток активен. Либо метод Join(), который заставляет поток в котором он вызван ожидать завершения работы потока которому он принадлежит.

Однако мы можем и сами менять виды потоков. Свойство потока IsBackground определяет является ли поток фоновым. Таким образом, мы можем сделать поток приоритетным. myThread.IsBackground = false;Однако, нам необязательно создавать новый поток внутри метода Main. Давайте создадим класс который будет открывать новый поток в собственном конструкторе. Созданный нами класс по-прежнему будет заниматься счетом, но на этот раз он будет считать до указанного числа, за счет передачи потоку параметров.

using System;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        class myThread 
        {
            Thread thread;

            public myThread(string name, int num) //Конструктор получает имя функции и номер до кторого ведется счет
            {

                thread = new Thread(this.func);
                thread.Name = name;
                thread.Start(num);//передача параметра в поток
            }
            
            void func(object num)//Функция потока, передаем параметр
            {
                for (int i = 0;i < (int)num;i++ ) 
                {
                    Console.WriteLine(Thread.CurrentThread.Name + " выводит " + i);
                    Thread.Sleep(0);
                }
                Console.WriteLine(Thread.CurrentThread.Name + " завершился");
            }
        
        
        }     
             
        static void Main(string[] args)
        {
            myThread t1 = new myThread("Thread 1", 6);
            myThread t2 = new myThread("Thread 2", 3);
            myThread t3 = new myThread("Thread 3", 2);

            Console.Read();

        }

    }
}
Разберем данный пример. Конструктор нашего класса myThread принимает 2 параметра: строку, в которой мы определяем имя потока, и номер до которого будет вестись счет в цикле. В конструкторе мы создаем поток, связанный с функцией func данного объекта. Далее мы присваиваем нашему потоку имя, используя поле Name созданного потока. И запускаем наш поток, передав функции Start аргумент.

Обратите внимание на то, что функция вызываемая потоком может принимать только 1 аргумент, и только типа object. В функции func цикл for досчитывает до числа, переданного ему как аргумент, и поток завершается. В функции Main мы создаем 3 пробных объекта, работа которых выведет на консоль примерно следующий текст.

Thread 1 выводит 0
Thread 1 выводит 1
Thread 2 выводит 0
Thread 2 выводит 1
Thread 2 выводит 2
Thread 2 завершился
<…>

На этом примере я хочу закончить свою статью. И хотя много средств работы с потоками остались незатронутыми мной, я смею надеяться что информация представленная мной будет полезной.
Поделиться публикацией

Похожие публикации

Комментарии 16

    0
    > Либо метод Join (), который заставляет поток в котором он вызван ожидать завершения работы потока которому он принадлежит.

    Верно конечно, но ох как завернули-то.
      +17
      Статья закончилась не начавшись…
        0
        > На этом примере я хочу закончить свою статью. И хотя много средств работы с потоками остались незатронутыми мной…

        Будем надеяться, что последуют статьи об остальных средствах работы.
          +6
          Вот здесь лучший на это планете текст про потоки в шарпее.
            0
            +1. Но ты дал ссылку только на первую статью, а лучше бы на все три сразу: Часть 1, Часть 2, Часть 3.

              0
              Это если углубляться, а для сиюминутных нужд чайника статья AtheistATT самое то. Спасибо автору.
            0
            Очень слабо развернуто, конечно, но для «первых шагов» достаточно, спасибо. Жду «вторых и третьих» шагов.
              +1
              Вот про это еще стоит упомянуть в следующих частях (если они будут) — Task Parallel Library, входящую в состав 4 фреймворка. Ибо Р-LINQ очень нужен сейчас на многоядерных компах
                +1
                Если уж делаете статью для новичков, сделайте ее законченной. Чтобы раз прочитал и все вопросы отпали.
                Зачем на хабре очередная статья, напоминающая конспект студента?
                  +1
                  Как — зачем? Ради инвайта, очевидно же.
                  +5
                  Свойство потока IsBackground определяет является ли поток фоновым. Таким образом, мы можем сделать поток приоритетным. myThread.IsBackground = false

                  Не вводите людей в заблуждение, фоновый поток по приоритету не отличается от обычного (foreground). Разницы заключается в том, что незавершенный обычный поток не дает приложению закрыться, в то время как если остались работающими только фоновые потоки — то приложение будет закрыто.
                    0
                    Тоже когда-то давно видел это заблуждение в одной из книг, а потом долго думал, почему приложение зависает в памяти.
                      0
                      Вообще фоновые потоки — это bad pracice по тем же причинам, что и Thread.Abort. Если нужно завершить выполнение задач при закрытии программы — надо уведомить их установкой флага или как-то еще. А фоновые потоки использовать можно для чего-то разве каких-то задач не владеющих ресурсами совсем, которые при внезапной остановке не смогут ничему навредить…
                    +3
                    Да это же страница из любой книги по C#, ничего сложного\интересного\необычного тут нет.
                      0
                      Я когда статью про Qt Quick делал, хотя бы ориентировался на то, что литературы не фонтан, а книг вовсе ни одной…
                        0
                        Статья реально ни о чём.

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

                        Самое читаемое