Pull to refresh

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

Reading time 4 min
Views 343K
Уважаемые читатели, в этой статье я хочу рассказать о таком важном средстве многозадачного программирования среды .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 завершился
<…>

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

Articles