Наступил день, когда мне понадобилась многопоточность в C#. Я мог бы и не использовать в приложении многопоточность, но тогда в момент работы приложения создаётся впечатление, что приложение зависает в момент, когда приложение ожидает завершения какой-либо операции через .Wait().

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

Как работать с потоками в C#

После изучения данной темы, проб и ошибок, упрощения и написания кода всё оказалось достаточно просто. Сейчас постараюсь объяснить простыми словами, как запустить задачу в отдельном потоке.

Подключаем:

using System.Threading;

Далее у нас есть некая функция 1 (func1) в которой выполняется некий код и в середине этого кода у нас есть кусок кода, который мы хотим выполнить в отдельном потоке. Для этого нам необходимо написать дополнительную функция 2 (func2), в которую вынести весь код, который мы будем запускать в отдельном потоке. После этого в нужном месте функции 1 нам надо вызвать отдельный поток и продолжить выполнение кода, в то время, когда в отдельном потоке параллельно будет выполняться другой код. Теперь всё вышесказанное мы опишем кодом.

Функция 2:

public void func2()
{
    // здесь код, который будет выполняться в отдельном потоке
}

Функция 1:

public void func1()
{
    // КОД 1 - здесь код до запуска потока
    Thread potok1 = new Thread(func2); // создание отдельного потока
    potok1.Start(); // запуск потока
    // КОД 2 - здесь код после запуска первого потока
    Thread potok2 = new Thread(func3); // создание отдельного потока
    potok2.Start();
    // КОД 3 - здесь код после запуска второго потока
    potok1.Abort(); // остановка потока
    potok2.Abort(); // остановка потока
}

Функции привёл в обратном порядке, чтобы далее прокомментировать функцию 1. Сначала у нас выполняется некий «КОД 1» после этого мы запускаем первый поток и сразу продолжаем выполнять «КОД 2», потом доходим до запуска второго потока и его запускаем и сразу переходим к выполнению следующего кода «КОД 3». В итоге у нас два потока выполняют код из функций «func2» и «func3» одновременно с выполнением кода основной функции «func1».

В данном примере у нас после выполнения «КОД 3» происходит выход из функции 1 при этом потоки продолжают свою работу пока не выполнится весь вызываемый ими код. Есть способ, который позволяет при завершении главной функции, которая запустила потоки, сразу завершать и выполнение потоков, для этого добавляем потоку параметр «IsBackground = true»:

public void func1()
{
    // КОД 1 - здесь код до запуска потока
    Thread potok1 = new Thread(func2); // создание отдельного потока
    potok1.IsBackground = true; // завершить поток при завершении основного потока (объявлять, если точно знаете, что вам это нужно, иначе поток завершится не выполнив свою работу до конца)
    potok1.Start(); // запуск потока
    // КОД 2 - здесь код после запуска первого потока
}

Теперь у нас поток будет завершён сразу после выполнения «КОД 2».

Если вы хотите узнать в каком состоянии сейчас находится запущенный вами поток, то вам необходимо получить его статус «potok1.ThreadState». Подробное описание статусов можете просмотреть на сайте Microsoft.

Как в отдельном потоке в C# обращаться к элементам формы

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

Объявляем «MyDelegate»:

public partial class Form1 : Form
{
    public delegate void MyDelegate(); // для доступа к элементам из другого потока

    public Form1()
    {
        InitializeComponent();
    }
}

Обратите внимание, где он объявляется.

Теперь создадим функцию, в которой будем изменять требуемый нам элемент:

public void IzmeniElement()
{
    label2.Visible = false; // скрыли элемент
}

Теперь в коде отдельного потока мы с вами изменим элемент обратившись к функции изменяющий требуемый элемент:

public void func2()
{
    // здесь код, который будет выполняться в отдельном потоке и ...
    BeginInvoke(new MyDelegate(IzmeniElement)); // изменяем элемент
}

А что делать если нам надо изменить элемент передав в него какое-то значение? Для этого объявляем «MyDelegate» с переменной:

public partial class Form1 : Form
{
    public delegate void MyDelegate(string iText); // для доступа к элементам из другого потока с передачей параметров

    public Form1()
    {
        InitializeComponent();
    }
}

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

public void IzmeniElement(string iText)
{
    label2.Text = iText; // изменили текст элемента
}

Теперь в коде отдельного потока изменим элемент обратившись к функции изменяющий требуемый элемент и передав требуемое нами значение:

public void func2()
{
    // здесь код, который будет выполняться в отдельном потоке и ...
    BeginInvoke(new MyDelegate(IzmeniElement), "Мышь съела зерно"); // изменяем элемент передав ему значение
    // для WPF отличается, надо вот так
    this.Dispatcher.BeginInvoke(new MyDelegate(IzmeniElement), "Мышь съела зерно");
}

Основа заложена, а дальше уже остаётся самостоятельно постигать глубинный смысл потоков 🙂