Страницы

Поиск по вопросам

четверг, 26 декабря 2019 г.

Изменения GUI из разных потоков С#

#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(DoWork), data); else listbox.Items.Add((string)data); } На первый взгляд выглядит как рекурсия, но это не рекурсия, т.к. метод BeginInvoke ставит вызов делегата в очередь, элементы которой обрабатывается в UI-потоке. Другой пример передачи в UI-поток данных из разных потоков тут. Если надо отделить модель/данные от UI, то пример тут и тут.

Комментариев нет:

Отправить комментарий