Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: Павелъ от Ноябрь 13, 2017, 12:49



Название: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 13, 2017, 12:49
Доброго времени суток.

Имею дерево (QTreeView), которое содержит список сетей: 255.255,
Для каждой сети сегментов: 255.255.255,
Для каждого сегмента ip-адресов: 255.255.255.255

При клике на Item, отвечающий за сегмент, открывается QTableView, при открытии этой таблицы создаётся модель, содержащая строки с ip-адресами, и некоторой информацией об этом устройстве. Самый первый Item имеет тип IpStandardItemWithThread, наследуемый от QStandardItem и QObject.
При клике на другой item в дереве – строки в QTableView удаляются.

Суть в чём. В классе IpStandardItemWithThread я создаю объект класса Ping, который перемещаю в созданный в IpStandardItemWithThread поток (moveToThread()) .
В классе Ping имеется функция, в которой в бесконечном цикле происходит пингование ip-адреса.

Возникли 2 проблемы:

Во-первых, при таком коннекте connect(itemPing,&Ping::isPing,[this](QString,bool statusPing){}
Вываливаются вот такие ошибки.

Код:
QObject::connect: Cannot queue arguments of type 'QVector<int>'
(Make sure 'QVector<int>' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QVector<int>'
(Make sure 'QVector<int>' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QVector<int>'
(Make sure 'QVector<int>' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QVector<int>'
(Make sure 'QVector<int>' is registered using qRegisterMetaType().)

Как от них избавиться?
Во-вторых, как по дестрою грамотно завершить поток и удалить объекты классов QThread  и Ping? При нынешнем варианте после клика на другой item вылетает программа, хотя должен завершаться поток и должна очищаться память, выделенная под объекты.

Класс пинга:

Код:
#ifndef PING_H
#define PING_H

#include "winsock2.h"
#include "iphlpapi.h"
#include "icmpapi.h"

#include <QObject>
#include <QString>

class Ping : public QObject
{
    Q_OBJECT
private:

    HANDLE hIcmpFile;                       // Обработчик
    unsigned long ipaddr;     // Адрес назначения
    DWORD dwRetVal;                     // Количество ответов
    char SendData[32];       // Буффер отсылаемых данных
    LPVOID ReplyBuffer;              // Буффер ответов
    DWORD ReplySize;

    QString _ipAddress;
    int _pingInterval;
public:
    explicit Ping(QString ipAddress,int pingInterval,QObject *parent = 0);
    ~Ping();

signals:

    void isPing(QString ipAddress,bool is_p);

public slots:
    void windowsPing();
};

#endif // PING_H

#include "ping.h"
#include <QDebug>


Ping::Ping(QString ipAddress,int pingInterval,QObject *parent) :
    QObject(parent),
    _pingInterval(pingInterval),
    _ipAddress(ipAddress),
    ipaddr(INADDR_NONE),
    dwRetVal(0),
    //SendData("Data Buffer"),
    ReplyBuffer(NULL),
    ReplySize(0)
{
    strcpy(SendData,"Data Buffer");


    // Установка ip-адреса

    ipaddr = inet_addr(_ipAddress.toStdString().c_str());
    hIcmpFile = IcmpCreateFile(); // создание обработчика

    // Выделение памяти под буффер ответов

    ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
    ReplyBuffer = (VOID*) malloc(ReplySize);



}

Ping::~Ping()
{
    if(ReplyBuffer) free(ReplyBuffer);
}



void Ping::windowsPing()
{
    while(true)
    {
        // Вызов функции ICMP эхо запроса
        dwRetVal = IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData), NULL, ReplyBuffer, ReplySize, 100);
        emit isPing(_ipAddress,(dwRetVal != 0));
        Sleep(_pingInterval);
    }
}

Класс модели таблицы:
Код:
#ifndef SEGMENTMODEL_H
#define SEGMENTMODEL_H

#include <QObject>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QModelIndex>
#include <QStringList>
#include <QIcon>
#include <QThread>


#include "centralmodel.h"
#include "ping.h"

class SegmentModel;
class IpStandardItemWithThread;

class SegmentModel : public QStandardItemModel
{
    Q_OBJECT
public:
    SegmentModel(QObject *parent = NULL);
private:
    QModelIndex _segmentModel;
    QString _segmentAddress;
    QString _segmentName;

    void insertRow(HostInfo ipInf);

public slots:
    void setNewSegment(QModelIndex segmentModel);
    void clearAll();
};



class IpStandardItemWithThread : public QStandardItem, public QObject
{
public:
    IpStandardItemWithThread(QString ipAddress,int pingInterval);
    ~IpStandardItemWithThread();
private:
    QString _ipAddress;
    int _pingInterval;
    QThread *ipThread;
    Ping *itemPing;
};



#endif // SEGMENTMODEL_H

#include "segmentmodel.h"

SegmentModel::SegmentModel(QObject *parent):
    QStandardItemModel(parent)
{
    this->setHorizontalHeaderLabels(QStringList() << tr("Д") << tr("IP") << tr("NetBIOS") << tr("MAC") << tr("Тип устройства") << tr("Операционная\nсистема") << tr("Комментарий") << tr("Р"));
}

void SegmentModel::insertRow(HostInfo ipInf)
{

    IpStandardItemWithThread *item0 = new IpStandardItemWithThread(ipInf.IpAddress,3000);
    item0->setEditable(false);
    item0->setSelectable(false);

    QStandardItem *item1 = new QStandardItem(ipInf.IpAddress);
    item1->setEditable(false);
    item1->setSelectable(false);

    QStandardItem *item2 = new QStandardItem(ipInf.NetBIOS);
    item2->setEditable(false);
    item2->setSelectable(false);

    QStandardItem *item3 = new QStandardItem(ipInf.MAC);
    item3->setEditable(false);
    item3->setSelectable(false);

    QStandardItem *item4 = new QStandardItem(ipInf.DeviceType);
    item4->setEditable(false);
    item4->setSelectable(false);

    QStandardItem *item5 = new QStandardItem(ipInf.OS);
    item5->setEditable(false);
    item5->setSelectable(false);

    QStandardItem *item6 = new QStandardItem(ipInf.comment);
    item6->setEditable(false);
    item6->setSelectable(false);

    QStandardItem *item7 = new QStandardItem("");
    item7->setEditable(false);
    item7->setSelectable(false);

    this->appendRow(QList<QStandardItem *>() << item0 << item1 << item2 << item3 << item4 << item5 << item6 << item7);
}

void SegmentModel::setNewSegment(QModelIndex segmentModel)
{
    _segmentModel = segmentModel;
    _segmentAddress = _segmentModel.data().toString();
    _segmentName = _segmentModel.sibling(_segmentModel.row(),1).data().toString();


    QStandardItem *segmentItem = ((CentralModel*)(_segmentModel.model()))->itemFromIndex(_segmentModel);


    for(int i=0;i<segmentItem->rowCount();i++)
    {
        IpStandardItem *ipItem = (IpStandardItem *)segmentItem->child(i,0);
        insertRow(ipItem->getIpInformation());
    }



}

void SegmentModel::clearAll()
{
    this->removeRows(0,this->rowCount());
}

IpStandardItemWithThread::IpStandardItemWithThread(QString ipAddress, int pingInterval):
    _ipAddress(ipAddress),
    _pingInterval(pingInterval)
{
    this->setIcon(QIcon(":/icons/ping_unknown.png"));

    ipThread = new QThread();

    itemPing = new Ping(_ipAddress,_pingInterval);

    itemPing->moveToThread(ipThread);


    connect(itemPing,&Ping::isPing,[this](QString,bool statusPing){
        if(statusPing)
        {
            this->setIcon(QIcon(":/icons/ping_on.png"));
        }
        else
        {
            this->setIcon(QIcon(":/icons/ping_off.png"));
        }
    });

    connect(ipThread,SIGNAL(started()),itemPing,SLOT(windowsPing()));

    connect(ipThread,SIGNAL(finished()),itemPing,SLOT(deleteLater()));

    ipThread->start();
}

IpStandardItemWithThread::~IpStandardItemWithThread()
{

    if(ipThread->isRunning())
    {
        ipThread->terminate();

    }
}


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: kuzulis от Ноябрь 13, 2017, 13:11
А не проще ли юзать QProcess && утилиту ping?


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 13, 2017, 15:05
А не проще ли юзать QProcess && утилиту ping?

А как это поможет в решении моей проблемы?


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: __Heaven__ от Ноябрь 13, 2017, 15:24
А как это поможет в решении моей проблемы?
Товарищ kuzulis предлагает использовать процесс вместо потока. А за освобождение ресурсов процесса заботится уже ОС.


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 13, 2017, 16:32
Не подходит этот вариант из-за того, что системный пинг (то бишь утилита ping) требует определённое время для своей работы.

Через winAPI делается это очень быстро.

Я уже не знаю. Начитался супер "мудрых" статей о том, что согласно ООП не кошерно наследовать QThread и переопределять run(). Сделал по их статьям movetothread(). А как грамотно завершить поток так никто нигде не может сказать.

Неужели такая жесть с этими потоками?

Как его просто завершить?

Я уже вместо true использую bool переменную в while, чтобы выйти из цикла.

И всё равно thread.isRunning().

Как его завершить-то?


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Igors от Ноябрь 13, 2017, 17:00
Я уже вместо true использую bool переменную в while, чтобы выйти из цикла.
Способ правильный и фактически единственный. Нитка должна сама завершиться, вынудить ее - себе дороже
И всё равно thread.isRunning().
Значит что-то не так. Остановитесь в отладчике и посмотрите что происходит


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: kuzulis от Ноябрь 13, 2017, 17:13
Цитировать
требует определённое время для своей работы.

зависит от аргументов.. можно и навечно запустить


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: __Heaven__ от Ноябрь 13, 2017, 23:45
Код:
ping /?


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: qate от Ноябрь 14, 2017, 08:16
Через winAPI делается это очень быстро.

аватарку пингвина для отпугивания bsd демонов поставил ? )

а если через потоки, так тут qtconcurrent напрашивается


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 14, 2017, 11:21


аватарку пингвина для отпугивания bsd демонов поставил ? )


На работе ОС выбирать не приходится


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 14, 2017, 11:25
а если через потоки, так тут qtconcurrent напрашивается

Есть простейший пример запуска QConcurent и завершения по сигналу или дестрою?


Допустим есть класс, в котором есть функция doWork
в ней

while(true)
{
ping;
emit status_ping();
}


Как это реализовать?


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: zhbr от Ноябрь 14, 2017, 11:38

Во-первых, при таком коннекте connect(itemPing,&Ping::isPing,[this](QString,bool statusPing){}
Вываливаются вот такие ошибки.

Как от них избавиться?
чтонить типа `qRegisterMetaType<QVector<int>>("QVector<int>");` вставить кудато в начало программы (главное чтобы выполнилось до connect)

Во-вторых, как по дестрою грамотно завершить поток и удалить объекты классов QThread  и Ping?

по дестрою чего?

я бы запускал пинг через QtConcurent::run().

но если хочешь с QThread, то важно чтобы метод Ping::windowsPing() закончил выполнение (прервать выполнение цикла либо через условие в заголовке цикла, либо внутри break по условию), затем вызвать thread.quit() (можно в конце Ping::windowsPing() эмитировать сигнал, предварительно связав его с thread.quit()), ну и потом можно удалять объекты.



Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: qate от Ноябрь 14, 2017, 13:28
Как это реализовать?

Код:
QFutureWatcher<QByteArray>* fw = new QFutureWatcher<QByteArray>(this);

fw->setFuture(QtConcurrent::run ( [=]() {

// YOUR CODE HERE

QProcess p;
p.start("/bin/bash", QStringList() << "-c" << "ping -c 1 -w 2 8.8.8.8");
p.waitForFinished();
return p.readAll().simplified();
}));

connect(fw, &QFutureWatcher<QString>::finished, this, [=] {
qDebug() << fw->result();
});


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 14, 2017, 16:01
Как это реализовать?

Код:
QFutureWatcher<QByteArray>* fw = new QFutureWatcher<QByteArray>(this);

fw->setFuture(QtConcurrent::run ( [=]() {

// YOUR CODE HERE

QProcess p;
p.start("/bin/bash", QStringList() << "-c" << "ping -c 1 -w 2 8.8.8.8");
p.waitForFinished();
return p.readAll().simplified();
}));

connect(fw, &QFutureWatcher<QString>::finished, this, [=] {
qDebug() << fw->result();
});

Вынуждаете использовать системный пинг)))
Ну, ладно, буду его использовать.

Я в Windows столкнулся с такой проблемой, что в зависимости от языка ОС - вывод в терминале пинга - тоже на разных языках.

А это значит, что обработка вывода будет привязана к языку. А там особо привязаться не к чему при парсинге.

В Линукс в этом плане лучше - вывод пинга всегда на английском.


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: qate от Ноябрь 14, 2017, 16:24
Я в Windows столкнулся с такой проблемой, что в зависимости от языка ОС - вывод в терминале пинга - тоже на разных языках.

chcp 437 && ping -n 1 -w 2 8.8.8.8


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 15, 2017, 09:31
Я в Windows столкнулся с такой проблемой, что в зависимости от языка ОС - вывод в терминале пинга - тоже на разных языках.

chcp 437 && ping -n 1 -w 2 8.8.8.8


А как запустить эти 2 процесса вместе в QProcess? Не получается.


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 15, 2017, 10:51
Сделал процесс в QConcurrent::run().

Однако, при удалении моего объекта класса, наследуемого от QStandardItem (в котором всё и происходит), вылетает программа.
Ошибки связаны с памятью.

ASSERT: "pid" in file io\qprocess_win.cpp, line 771
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.

QWaitCondition: Destroyed while threads are still waiting
QWaitCondition: Destroyed while threads are still waiting
QWaitCondition: Destroyed while threads are still waiting
QWaitCondition: Destroyed while threads are still waiting

Вот мой участок кода, отвечающий за это.


Код:
IpStandardItemWithThread::IpStandardItemWithThread(QString ipAddress, int pingInterval):
    _ipAddress(ipAddress),
    _pingInterval(pingInterval),
    unknownIcon(QIcon(":/icons/ping_unknown.png")),
    onIcon(QIcon(":/icons/ping_on.png")),
    offIcon(QIcon(":/icons/ping_off.png"))
{
    this->setIcon(unknownIcon);

    QFutureWatcher<QByteArray>* fw = new QFutureWatcher<QByteArray>(this);

    connect(fw, &QFutureWatcher<QString>::finished, this, [=] {
        qDebug() << fw->result();
    });



    fw->setFuture(QtConcurrent::run ( [=]() {

        // YOUR CODE HERE

        QProcess p;

        connect(this,&IpStandardItemWithThread::closeProcess,[this,&p](){
            if(p.isOpen())
            {
                p.kill();
                p.waitForFinished();

                qDebug() << p.isOpen();
            }
        });


        p.start("C:/Windows/System32/PING.EXE",QStringList() << "-n" << "1" << "-w" << "2" << _ipAddress);




        qApp->processEvents();
        p.waitForFinished();
        qApp->processEvents();


            return p.readAll().simplified();
    }));

}

IpStandardItemWithThread::~IpStandardItemWithThread()
{
    //itemPing->setWorkFalse();

    emit closeProcess();
}


Суть в чём.
Ещё раз повторюсь.
Есть объект QTableView.
Каждый раз при клике на айтем второго уровня в QTreeView у меня создаются айтемы для QTableView. При клике на другой айтем в QTreeView – вызываю функцию clearAll, которая делает removerows для модели QTableView. То, есть удаляет все айтемы в QTableView.

Один из айтемов в модели QTableView я изменил.
Сделал наследование от QStandardItem, код этого изменённого класса представлен выше.

То, есть в айтеме у меня создаётся процесс, который автоматически должен удаляться при удалении айтема в деструкторе. Но, программа вылетает.

Собственно, что мне надо. Чтобы происходил пинг для каждого ip-адреса в таблице и  прекращался при закрытии таблицы. А после прекращения, чтобы очищалась память, выделенная под пинги в айтеме.


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: qate от Ноябрь 15, 2017, 12:42
Я в Windows столкнулся с такой проблемой, что в зависимости от языка ОС - вывод в терминале пинга - тоже на разных языках.

chcp 437 && ping -n 1 -w 2 8.8.8.8


А как запустить эти 2 процесса вместе в QProcess? Не получается.

это не два процесса, а один - это команда для cmd.exe /с


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 15, 2017, 14:54
Я в Windows столкнулся с такой проблемой, что в зависимости от языка ОС - вывод в терминале пинга - тоже на разных языках.

chcp 437 && ping -n 1 -w 2 8.8.8.8


А как запустить эти 2 процесса вместе в QProcess? Не получается.

это не два процесса, а один - это команда для cmd.exe /с


Так

Код:
proc->start("C:/Windows/System32/CMD.EXE",QStringList() << "/c" << "C:/Windows/System32/CHCP.COM" << "437" << "&&" << "C:/Windows/System32/PING.EXE" << "-n" << "1" << "-w" << "2" <<  _ipAddress);

не работает.

Так
Код:
proc->start("C:/Windows/System32/CMD.EXE",QStringList() << "/c" << "C:/Windows/System32/CHCP.COM 437" << "&&" << "C:/Windows/System32/PING.EXE -n 1 -w 2 " +  _ipAddress);

тоже не работает.

Для одного пинга работает:
Код:
proc->start("C:/Windows/System32/PING.EXE",QStringList() << "-n" << "1" << "-w" << "2" << _ipAddress);


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: qate от Ноябрь 15, 2017, 15:58
process.start("cmd", QStringList() << "/C" << "chcp 437 && ping -n 1 -w 2 8.8.8.8");


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 15, 2017, 21:03
process.start("cmd", QStringList() << "/C" << "chcp 437 && ping -n 1 -w 2 8.8.8.8");

Да, это работает.

Только не завершается процесс при уничтожении айтема, из-за этого вылетает программа.


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: zhbr от Ноябрь 16, 2017, 07:27

Код:
        qApp->processEvents();
        p.waitForFinished();
        qApp->processEvents();


processEvents тут не нужен, так как внутри потока запущенного через QtConcurent::run нет ни живущих объектов ни цикла обработки событий.


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 16, 2017, 15:40

Код:
        qApp->processEvents();
        p.waitForFinished();
        qApp->processEvents();


processEvents тут не нужен, так как внутри потока запущенного через QtConcurent::run нет ни живущих объектов ни цикла обработки событий.

спасибо


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 16, 2017, 15:41
Кажется, я понял, в чём дело.

Я создаю цикл в QConcurrent::run() и в условиях цикла пишу переменную, по которой собираюсь выйти из цикла.
Но, суть в том, что если я изменяю значение переменной за пределами QConcurrent::run(), в самом QConcurrent::run() она остаётся всё время одинаковой.
Как будто первый раз при создании QConcurrent::run() туда передались копии всех внешних переменных.
Я и по указателю пытался передавать переменную work, и писал функцию возвращающую текущее значение work, всё бесполезно – внутри переменные всегда одинаковые.

В связи с чем возникли 2 новых вопроса.
1)   Как сделать, чтобы в  QConcurrent::run() переменные ОБНОВЛЯЛИСЬ? А то у меня подозрения, что из-за этого и пинг некорректно определяется.
2)   Как замедлить выполнение в цикле while внутри QConcurrent::run(), чтобы скажем раз в 500 мс выполнялось тело цикла? Никаких sleep() внутри QStandardItem нет.
Спасибо.

Код:
class IpStandardItemWithThread : public QStandardItem
{
public:
    IpStandardItemWithThread(QString ipAddress,int pingInterval);
    ~IpStandardItemWithThread();
private:
    QString _ipAddress;
    int _pingInterval;
    QIcon unknownIcon, onIcon,offIcon;

    HANDLE hIcmpFile;                       // Обработчик
    unsigned long ipaddr;     // Адрес назначения                    // Количество ответов
    char SendData[32];       // Буффер отсылаемых данных
    LPVOID ReplyBuffer;              // Буффер ответов
    DWORD ReplySize;

    QFuture<void> future;

    bool work;
};



Код:
IpStandardItemWithThread::IpStandardItemWithThread(QString ipAddress, int pingInterval):
    _ipAddress(ipAddress),
    _pingInterval(pingInterval),
    unknownIcon(QIcon(":/icons/ping_unknown.png")),
    onIcon(QIcon(":/icons/ping_on.png")),
    offIcon(QIcon(":/icons/ping_off.png")),
    ipaddr(INADDR_NONE),
    ReplyBuffer(NULL),
    ReplySize(0),
    work(true)
{

    this->setIcon(unknownIcon);


    strcpy(SendData,"Data Buffer");


    // Установка ip-адреса

    ipaddr = inet_addr(_ipAddress.toStdString().c_str());
    hIcmpFile = IcmpCreateFile(); // создание обработчика

    // Выделение памяти под буффер ответов

    ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
    ReplyBuffer = (VOID*) malloc(ReplySize);


    future = QtConcurrent::run([this](){

        while(work)
        {
            if(IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData), NULL, ReplyBuffer, ReplySize, 100))
            {
                this->setIcon(onIcon);
                qDebug() << _ipAddress << work;
            }
            else
            {
                this->setIcon(offIcon);
                qDebug() << _ipAddress << work;
            }
        }
    });

}

IpStandardItemWithThread::~IpStandardItemWithThread()
{
    work = false;
    future.waitForFinished();

    if(ReplyBuffer)
    {
        free(ReplyBuffer);
        ReplyBuffer = NULL;
    }
}



Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: qate от Ноябрь 16, 2017, 15:48
1. лучше взять тогда поток, если хочется между ними бросать изменения переменных
2. тогда и замедление не нужно - будут события


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 16, 2017, 17:07
1. лучше взять тогда поток, если хочется между ними бросать изменения переменных
2. тогда и замедление не нужно - будут события

А насколько корректным является то, что я сделал теперь?

Я взял таймер и засунул в него QFuture. Форма не виснит.

Код:
#include "segmentmodel.h"
#include "segmentwidget.h"


SegmentModel::SegmentModel()
{
    this->setHorizontalHeaderLabels(QStringList() << tr("Д") << tr("IP") << tr("NetBIOS") << tr("MAC") << tr("Тип устройства") << tr("Операционная\nсистема") << tr("Комментарий") << tr("Р"));

}

void SegmentModel::insertRow(HostInfo ipInf)
{

    IpStandardItemWithThread *item0 = new IpStandardItemWithThread(ipInf.IpAddress,1000);
    item0->setEditable(false);
    item0->setSelectable(false);


    connect(item0,SIGNAL(canRepaint()),this,SIGNAL(canRepaint()));

    QStandardItem *item1 = new QStandardItem(ipInf.IpAddress);
    item1->setEditable(false);
    item1->setSelectable(false);

    QStandardItem *item2 = new QStandardItem(ipInf.NetBIOS);
    item2->setEditable(false);
    item2->setSelectable(false);

    QStandardItem *item3 = new QStandardItem(ipInf.MAC);
    item3->setEditable(false);
    item3->setSelectable(false);

    QStandardItem *item4 = new QStandardItem(ipInf.DeviceType);
    item4->setEditable(false);
    item4->setSelectable(false);

    QStandardItem *item5 = new QStandardItem(ipInf.OS);
    item5->setEditable(false);
    item5->setSelectable(false);

    QStandardItem *item6 = new QStandardItem(ipInf.comment);
    item6->setEditable(false);
    item6->setSelectable(false);

    QStandardItem *item7 = new QStandardItem("");
    item7->setEditable(false);
    item7->setSelectable(false);

    this->appendRow(QList<QStandardItem *>() << item0 << item1 << item2 << item3 << item4 << item5 << item6 << item7);
}

void SegmentModel::setNewSegment(QModelIndex segmentModel)
{
    _segmentModel = segmentModel;
    _segmentAddress = _segmentModel.data().toString();
    _segmentName = _segmentModel.sibling(_segmentModel.row(),1).data().toString();


    QStandardItem *segmentItem = ((CentralModel*)(_segmentModel.model()))->itemFromIndex(_segmentModel);


    for(int i=0;i<segmentItem->rowCount();i++)
    {
        IpStandardItem *ipItem = (IpStandardItem *)segmentItem->child(i,0);
        insertRow(ipItem->getIpInformation());
    }

}


void SegmentModel::insertClearRow()
{
    if(this->rowCount() < 254)
    {
        this->setRowCount(this->rowCount() + 1);
    }
}


IpStandardItemWithThread::IpStandardItemWithThread(QString ipAddress, int pingInterval):
    _ipAddress(ipAddress),
    _pingInterval(pingInterval),
    unknownIcon(QIcon(":/icons/ping_unknown.png")),
    onIcon(QIcon(":/icons/ping_on.png")),
    offIcon(QIcon(":/icons/ping_off.png")),
    ipaddr(INADDR_NONE),
    ReplyBuffer(NULL),
    ReplySize(0),
    testTimer(new QTimer())
{

    this->setIcon(unknownIcon);


    strcpy(SendData,"Data Buffer");


    // Установка ip-адреса

    ipaddr = inet_addr(_ipAddress.toStdString().c_str());
    hIcmpFile = IcmpCreateFile(); // создание обработчика

    // Выделение памяти под буффер ответов

    ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
    ReplyBuffer = (VOID*) malloc(ReplySize);




    connect(testTimer,&QTimer::timeout,[this](){

        future = QtConcurrent::run(this,testFunction);

    });


    testTimer->start(_pingInterval);

   

}

IpStandardItemWithThread::~IpStandardItemWithThread()
{
    testTimer->stop();

    future.waitForFinished();

    if(ReplyBuffer)
    {
        free(ReplyBuffer);
        ReplyBuffer = NULL;
    }

    if(testTimer)
    {
        delete testTimer;
        testTimer = NULL;
    }
}

void IpStandardItemWithThread::testFunction()
{
    if(IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData), NULL, ReplyBuffer, ReplySize, 100))
    {
        this->setIcon(onIcon);
    }
    else
    {
        this->setIcon(offIcon);
    }

    emit canRepaint();
}



Только есть небольшая проблемка. Перерисовка иконок периодически в айтемах не происходит. Никаких методов типа repaint в QStandardItem нет.
Когда кликаю мышкой на айтем, то идёт перерисовка.
Я даже сделал высулку сигнала после добавления иконки. Но, дело в том, что таких айтемов одновременно может быть 254.
Перерисовка всего QTreeView не хорошее дело. Всё зависает.

Как можно обновить отображение айтема с иконкой?


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: kuzulis от Ноябрь 16, 2017, 19:00
Много говно-говно-кода, и, это все напоминает попытку наговнокодить еще больше, чтобы уже переливалось и "втереть нам какую-то дичь" (с). Нет желания ну никакого копаться в коде.


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 16, 2017, 21:26
Много говно-говно-кода, и, это все напоминает попытку наговнокодить еще больше, чтобы уже переливалось и "втереть нам какую-то дичь" (с). Нет желания ну никакого копаться в коде.

тут говнокод только winAPIшный сишный участок, отвечающий за пинг, который я урвал в сети, и не знаю, что там происходит.
Что так вызвало отвращение, malloc что ли?
Я действовал по-принципу: работает - не трожь.

Или эта инициализация в конструкторе дико смотрится?

Код:
    _ipAddress(ipAddress),
    _pingInterval(pingInterval),
    unknownIcon(QIcon(":/icons/ping_unknown.png")),
    onIcon(QIcon(":/icons/ping_on.png")),
    offIcon(QIcon(":/icons/ping_off.png")),
    ipaddr(INADDR_NONE),
    ReplyBuffer(NULL),
    ReplySize(0),
    testTimer(new QTimer())

А так, всё чисто Qtшное.

Да, и не в этом дело.

Вопрос был в том, что если я в QTimer вызываю QConcurrent::run() - так нормально?

Получается, что всё чистится и закрывается.

И это не самое плохое решение на мой взгляд.

Если с QProcess мутить, то вывод его обрабатывается дольше, да и с неудачным завершением программы потом приходится в диспетчере задач удалять cmd.exe, ping.exe, chcp.com.

Завтра, как вариант, попробую repaint QTableView по таймеру сделать, чтобы всё-таки иконки в айтемах обновлялись не только при клике мышкой.


Название: Re: Как закрыть поток и очистить выделенную под него память в деструкторе?
Отправлено: Павелъ от Ноябрь 22, 2017, 13:38
Давно не было меня здесь.

С таймером мутить было глупой идеей, так как поток не успевал завершиться, а таймер создавал новый.

Проблему я всё-таки решил с помощью QThread.
Переменную в цикле while, по которой необходимо выходить, объявил как volatile bool.

Использовал также QMutex.

Выкладываю участок своего говнокода, может кому-то понадобится сама идея. И да, всё-таки пришлось наследовать QThread и переопределять run(), хотя на Хабре этого делать не советуют.

Заголовочник
Код:
#ifndef SEGMENTMODEL_H
#define SEGMENTMODEL_H


#include <QObject>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QModelIndex>
#include <QStringList>
#include <QIcon>

#include <QThreadPool>


#include <QThread>

#include <QMutex>


#include "centralmodel.h"


class SegmentModel;
class IpStandardItemWithThread;
class PingThread;

class SegmentModel : public QStandardItemModel
{
    Q_OBJECT
public:
    SegmentModel(QObject *parent = nullptr);

    QString getSegmentAddress();

private:
    QModelIndex _segmentModel;
    QString _segmentAddress;
    QString _segmentName;


public slots:
    void setNewSegment(QModelIndex segmentModel);
    void insertRow(HostInfo ipInf);
    void removeIp(QString);

    void clearAll();
};



class IpStandardItemWithThread : public QObject, public QStandardItem
{
    Q_OBJECT
public:
    IpStandardItemWithThread(QString ipAddress,int pingInterval);
    ~IpStandardItemWithThread();
private:
    PingThread *_pThread;

    QIcon unknownIcon, onIcon,offIcon;
private slots:
    void setItemIcon(int);

};



class PingThread : public QThread
{
    Q_OBJECT

public:
    PingThread(QString ipAd,int ipInterval,QObject * parent = nullptr);
private:
    int _pingInterval;
    QString _ipAddress;
    volatile bool _stopped;
    QMutex mutex;
    void run();
signals:
    void statusPing(int);
public slots:
    void stop();
};




#endif // SEGMENTMODEL_H


сипипишник

Код:
#include "segmentmodel.h"
#include "segmentwidget.h"


SegmentModel::SegmentModel(QObject *parent):
    QStandardItemModel(parent)
{
}

QString SegmentModel::getSegmentAddress()
{
    return _segmentAddress;
}

void SegmentModel::insertRow(HostInfo ipInf)

    if(CommonFunctions::getSegmentFromIp(ipInf.IpAddress) != _segmentAddress) return;

    this->appendRow(QList<QStandardItem *>()
                    << new IpStandardItemWithThread(ipInf.IpAddress,1000)
                    << new QStandardItem(ipInf.IpAddress)
                    << new QStandardItem(ipInf.NetBIOS)
                    << new QStandardItem(ipInf.MAC)
                    << new QStandardItem(ipInf.DeviceType)
                    << new QStandardItem(ipInf.OS)
                    << new QStandardItem(ipInf.comment)
                    << new QStandardItem(""));

    for(int i=0;i<this->columnCount();i++)
        this->item(this->rowCount() - 1,i)->setEditable(false);

}

void SegmentModel::removeIp(QString ipAddr)
{
    if(CommonFunctions::getSegmentFromIp(ipAddr) != _segmentAddress) return;
    for(int i=0;i<this->rowCount();i++)
    {
        if(this->item(i,1)->text() == ipAddr)
        {
            this->removeRow(i);
            return;
        }
    }
}

void SegmentModel::clearAll()
{
    this->clear();
    _segmentName = "";
}

void SegmentModel::setNewSegment(QModelIndex segmentModel)
{
    this->setHorizontalHeaderLabels(QStringList()
                                    << tr("Д")
                                    << tr("IP")
                                    << tr("NetBIOS")
                                    << tr("MAC")
                                    << tr("Тип устройства")
                                    << tr("Операционная\nсистема")
                                    << tr("Комментарий")
                                    << tr("Р"));

    _segmentModel = segmentModel;


    _segmentAddress = _segmentModel.data().toString();

    _segmentName = _segmentModel.sibling(_segmentModel.row(),1).data().toString();



    QStandardItem *segmentItem = ((CentralModel*)(_segmentModel.model()))->itemFromIndex(_segmentModel);

    int i;

    for(i=0;i<segmentItem->rowCount();i++)
    {
        IpStandardItem *ipItem = (IpStandardItem *)segmentItem->child(i,0);
        insertRow(ipItem->getIpInformation());
    }
}

IpStandardItemWithThread::IpStandardItemWithThread(QString ipAddress, int pingInterval):
    unknownIcon(QIcon(":/icons/ping_unknown.png")),
    onIcon(QIcon(":/icons/ping_on.png")),
    offIcon(QIcon(":/icons/ping_off.png"))

{
    this->setIcon(unknownIcon);

    _pThread = new PingThread(ipAddress,pingInterval,this);


    connect(_pThread,SIGNAL(statusPing(int)),this,SLOT(setItemIcon(int)));

    _pThread->start();

}

IpStandardItemWithThread::~IpStandardItemWithThread()
{
    if(_pThread->isRunning())
    {
        _pThread->stop();

        _pThread->terminate();

        _pThread->wait();

    }
}

void IpStandardItemWithThread::setItemIcon(int pStatus)
{
    if(pStatus)
    {
        this->setIcon(onIcon);
    }
    else
    {
        this->setIcon(offIcon);
    }
}

PingThread::PingThread(QString ipAd, int ipInterval, QObject *parent):
    QThread(parent),
    _pingInterval(ipInterval),
    _ipAddress(ipAd),
    _stopped(false)
{

}


void PingThread::run()
{
    for(;;)
    {
        mutex.lock();

        if(_stopped)
        {
            _stopped = false;
            mutex.unlock();
            break;
        }
        mutex.unlock();

        msleep(_pingInterval);

        emit statusPing(CommonFunctions::isPingActive(_ipAddress));
    }
}

void PingThread::stop()
{
    mutex.lock();
    _stopped = true;
    mutex.unlock();
}