#c_sharp #многопоточность #gui
class test
{
public List threads = new List();
public int nThreads = 0;
public int maxThreads = 5;
public void DoWork(object data)
{
string message = (string)data;
//MessageBox.Show(message);
}
public void CreateThread(object data)
{
if (nThreads >= maxThreads)
return;
Thread newThread = new Thread(DoWork);
threads.Add(newThread);
newThread.IsBackground = true;
newThread.Start(data);
nThreads++;
}
public void WindUpThreads()
{
//MessageBox.Show("count: " + nThreads.ToString());
for(int i = 0; i < threads.Count; i++)
{
if (threads[i].IsAlive == false)
{
threads[i].Abort();
threads.RemoveAt(i);
//MessageBox.Show("removing at " + i.ToString());
}
}
nThreads = threads.Count;
}
}
Мне нужно в методе DoWork изменять данные в listbox, но я не хочу привязывать логику
к элементам управления. Как лучше поступить в таком случае?
В общем я решил пойти самым простым путем и передал делегат в тред:
public delegate void TestDeleg(string message);
public class Data
{
public TestDeleg deleg;
public string mess;
}
public void DoWork(object data)
{
Data d = (Data)data;
d.deleg(d.mess);
//string message = (string)data;
//MessageBox.Show(d.mess);
return;
}
В цикле теперь передаю класс с запакованным данными:
Data d = new Data();
d.deleg = AddItem;
d.mess = strings[counter];
thTest.CreateThread((object)d);
Ну и сам метод, который добавляет строку в листбокс:
public void AddItem(string message)
{
//listBox1.Items.Add(message);
if(InvokeRequired)
listBox1.Invoke( (Action)( () => { listBox1.Items.Add(message); } ) );
else
listBox1.Items.Add(message);
}
Теперь почему-то цикл никогда не заканчивается и треды не завершаются, метод, который
должен очищать треды почему-то не срабатывает.
UPD: Кажется, причина, по которой интерфейс не обновлялся, выяснилась. Основная часть
моей программы была такой:
while (flag == true)
{
if (counter >= dataCount)
{
flag = false;
}
while (thTest.nThreads < thTest.maxThreads)
{
if (flag == false)
break;
thTest.CreateThread(strings[counter]);
counter++;
}
thTest.WindUpThreads();
if (flag == false)
{
do
{
thTest.WindUpThreads();
} while (thTest.nThreads != 0);
}
}
И запускался этот код по нажатию на кнопку. Получается, что цикл крутился в основном
потоке и поэтому ui не принимал сообщения об изменении. Когда я вынес уже этот код
в отдельный поток (получается один поток создает другие и управляет ими), то все стало
на свои места.
Ответы
Ответ 1
Нужно вашу логику отделить от представления. Представить ваше "сердце" приложения как некоторую штуку, которая принимает "команды" из UI и отправляет информацию об изменении состояния - "события". В главном потоке слушать события UI и отправлять в "сердце" соответствующие им команды; подписаться на события основной логики и в соответствий с происходящим перерисовывать ваш UI. Посмотрите на IObservable и Reactive Extensions. И общие замечания - не рекомендуется создавать потоки руками. Поток - это достаточно тяжелая сущность. Многопоточность реализуется через различные библиотеки, где примитивы несколько интереснее и более удобные, чем потоки. Куда смотреть - Rx, Task Parrallel Library; читать - Jeffrey Richter, CLR via C# последнюю редакцию, главу про многопоточность.Ответ 2
Завести класс, хранящий состояние DoWork для отображения. Передать в DoWork делегат или объект presenter, который будет отображать состояние на listbox (используя BeginInvoke) Вызывать периодически presenter из под DoWorkОтвет 3
Как вариант, вы можете в потоках класть данные для ListBox в какую-нибудь коллекцию из System.Collections.Concurrent, а в основном (GUI) треде брать оттуда элементы и пихать их в целевой ListBox. Лично я предпочел бы использовать ConcurrentQueueследующим образом: В потоках добавляем элементы. В GUI-треде по таймеру забираем и очищаем. Ответ 4
Мне нужно в методе DoWork изменять данные в listbox Если метод DoWork вызывается из разных потоков, то это можно проверить и данные передать в UI-поток, примерно так: public void DoWork(object data) { if (listbox.InvokeRequired) listbox.BeginInvoke(new Action
Комментариев нет:
Отправить комментарий