Наступил день, когда мне понадобилась многопоточность в 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), "Мышь съела зерно");
} Основа заложена, а дальше уже остаётся самостоятельно постигать глубинный смысл потоков 🙂









Не совсем. У меня основной поток идет на форме Form (UI). Есть отдельный класс, из которого функция запускается во втором потоке. Программе нужно запускать много копий потоков с разными входными данными и потом через делегаты пробрасывать в первый поток (основная форма) прогресс исполнения каждого потока. Причем это надо делать как из вторичных потоков, по ходу изменения прогресса, так и из основного потока получать по требованию статус любого из фоновых потоков.
Вот примерный код на текущий момент:
public partial class Form_Status : Form
{
private void DGV_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
int a,b,c
//Тут всякий код
Thread MyThread1 = new Thread(delegate () { Download(a,b,c); });
MyThread1.Start();
}
public void Download() //Этот метод, который содержит нужный экземпляр класса, запускается в отдельном потоке
{
Downloaders downloader = new Downloaders();
string[] result;
result = downloader.Download(a,b,c);
//Вот тут, по идее, должен быть Invoke, который передаст статус в основной поток.
}
public void TrackProgress(string[] result) // здесь через делегат мы обновляем прогрессбар
{
progressBar.value = result[1];
}
}
public class Downloaders
{
public delegate void ResultDelegate(string[] result); // делегат для обновления прогресс бара из основного потока
public string[] Download(a,b,c)
{
//Тут всякий код
return result;
}
}
А как быть если функция 1 и функция 2 находятся в разных классах?
Class2 _c2 = new Class2();
Thread potok1 = new Thread(_c2.func2);
Class2 это класс в котором находится func2
Это если я вас правильно понял.
BeginInvoke не работает
Смотрите чтобы правильно был объявлен:
public delegate void MyDelegate();Также смотрите, чтобы метод, к которому обращаетесь в BeginInvoke был доступен ему:
BeginInvoke(new MyDelegate(IzmeniElement));Читал много где, что работа с потоками нужна лишь в 10% случаев, в остальном лучше использовать Task. Можете пояснить про ваш случай, для чего там потоки нужны?
Если бы я был профессиональным разработчиком, то я бы озаботился вопросами, что использовать Thread или Task, Array или List…
Мне удобно было в определённой ситуации использовать потоки и я их использовал https://victorz.ru/20170507493
Были случаи использования Task, когда я из JIRA получал номера задач и далее в процессе написания автотестов проводил определённые операции.
Я не сторонник холиварить на тему «что лучше…». Каждый использует, то что считает нужным в определённый момент.
а как сделать так, чтобы форма не морозилась?
сделал в IzmeniElement цикл <100 с Thread.Sleep(3000);
вызываю в button1_Click, форма морозится.
Как это обойти?
Если быстро, то как-то так попробуй:
using System; using System.Windows.Forms; using System.Threading; namespace test_01 { public partial class Form1 : Form { public delegate void MyDelegate(); public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Thread potok1 = new Thread(func1); // создание отдельного потока potok1.IsBackground = true; potok1.Start(); // запуск потока } public void func1() { for (int i = 0; i < 100; i++) { Thread.Sleep(3000); } BeginInvoke(new MyDelegate(IzmeniElement)); // изменяем элемент } public void IzmeniElement() { label1.Visible = false; // скрыли элемент } }Сам много раз читал в разных источниках дебильные или пространные объяснения принципов многопоточности, спасибо автору, что у него всё в порядке и с пониманием физики процесса и с умением донести инфу до других.
Чувак Спасибо! Реально выручил!
Спасибо!
Спасибо!
Блин — это ЛУЧШЕЕ объяснение многопоточности и их вызовов в формах.
Спасибо
Самая доходчивая статья многопоточности C#.
Спасибо!
Эта статья мне очень помогла т.к. ранее не сталкивался с многопоточностью. Описано все очень доходчиво и просто. Спасибо =)