Russian Qt Forum

Qt => Общие вопросы => Тема начата: kdm от Апрель 15, 2010, 19:26



Название: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: kdm от Апрель 15, 2010, 19:26
(честно говоря не знал, где такую тему создавать, подумал, что таким, как я - сюда)

Дело состоит в том, что я пишу первое в жизни многопоточное приложение.
С потоками разбирался с книжкой по Qt, Примерам, Assistant'у, но так и не понимаю, как их использовать в конкретном для меня случае.
Прошу аудиторию форума помочь разобраться в этой ерунде, вторую неделю сижу, кучу листов изрисовал, так и не понял.

Вот для накидал небольшую промежуточную программку, которая по нажатию кнопки "загрузить" открывает диалоговое окно с файлами, а потом открывается диалоговое окно с label'ами на которых отображены текущий загружаемый файл и процент проделанной операции. Последнее диалоговое окно запускает один за одним потоки (то есть когда один отработает, он удаляется и создается и запускается другой и так далее). Когда перебраны все имена файлов, то диалоговое окно закрывается.

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

Код:
Код:
class TLoadProgress : public QDialog
{
Q_OBJECT
    // Обяъвления элементов управления пропущены за экономией места в посте

    QStringList FileNames;
    int FileIndex;
    bool ThreadCreated;

    TLoadThread* Thread;
public:
    explicit TLoadProgress (QStringList& file_names, QWidget *parent = 0);
    ~TLoadProgress ();

public slots:
    void on_thread_stop ();
    void on_thread_progress (int value);

};

TLoadProgress :: TLoadProgress(QStringList& file_names, QWidget *parent)
    : QDialog(parent), FileNames(file_names), FileIndex(0), ThreadCreated(false)
{
    // Создание элементов управления пропущены за экономией места в посте

    // Dialog Buttons
    QHBoxLayout* layoutDialogButtons = new QHBoxLayout();
    layoutMain->addLayout(layoutDialogButtons);

    layoutDialogButtons->addWidget(btnOK = new QPushButton("OK", this));
    layoutDialogButtons->addWidget(btnCancel = new QPushButton("Cancel", this));

    on_thread_stop();
}

void TLoadProgress :: on_thread_stop ()
{
    qDebug() << "on thread stop";
    if (ThreadCreated)
    {
        if (Thread->getExitCode() == 1)
        {
            qDebug() << "on thread stop: Error On Exit Code";
        }
        qDebug() << "on thread stop: delete thread";
        delete Thread;
        ThreadCreated = false;
    };

    if (FileIndex < FileNames.count())
    {
        Thread = new TLoadThread(FileNames.at(FileIndex++));
        ThreadCreated = true;
        connect(Thread, SIGNAL(finished()), this, SLOT(on_thread_stop()));
        connect(Thread, SIGNAL(progress(int)), this, SLOT(on_thread_progress(int)));
        Thread->start();
    }
    else
        done(0);
}

TLoadProgress :: ~TLoadProgress ()
{
    if (ThreadCreated)
    {
        qDebug() << "progress destructor: thread delete";
        Thread->deleteLater();
    }
}

void TLoadProgress :: on_thread_progress (int value)
{
    lblProgress->setText(QString().setNum(value));
}

Код:
class TLoadThread : public QThread
{
    Q_OBJECT

    QString FileName;
    QFile File;
    QTextStream TextStream;
    int ExitCode;
    bool StopRequested;

public:
    explicit TLoadThread (QString file_name, QObject *parent = 0);
    void run ();
    int getExitCode ();
    void requestStop ();
    ~TLoadThread ();

signals:
    void progress (int);

public slots:
};

TLoadThread :: TLoadThread(QString file_name, QObject *parent)
    : QThread(parent), FileName(file_name), ExitCode(0), StopRequested(false)
{
    File.setFileName(FileName);
    File.open(QIODevice::ReadOnly);

    TextStream.setDevice(&File);
}

void TLoadThread :: run ()
{
    qDebug() << "thread run: Continue Read File From Stream " << FileName;
    for (double i = 0.0; /*!StopRequested &&*/ i < 100.0; i+=0.001)
    {
        emit progress(i);
    }
    ExitCode = 0;
}

int TLoadThread :: getExitCode ()
{
    return ExitCode;
}

void TLoadThread :: requestStop ()
{
    StopRequested = true;
}

TLoadThread :: ~TLoadThread ()
{
    qDebug() << "thread destructor: thread destroy";
    File.close();
}

Код:
void TMainWindow :: on_btnFilesOpen_clicked ()
{
    QFileDialog dlg(this, "Open File");
    dlg.setFileMode(QFileDialog::ExistingFile);
    dlg.setFilter("Data Files (*.*)");
    dlg.setAcceptMode(QFileDialog::AcceptOpen);
    dlg.setFileMode(QFileDialog::ExistingFiles);

    if (dlg.exec())
    {
        QStringList file_names = dlg.selectedFiles();

        TLoadProgress* LoadProgress = new TLoadProgress(file_names, this);
        LoadProgress->exec();
    }

}

И еще совсем не понятные для меня вещи:
1) Почему когда вызывается запрос на завршение потока, то если exec() внутри run() не выполнялся, то функций ничего не делает, приходится свою функцию писать requestStop, которая переменную нужную выставляет в true, чтобы алгортм завершился. В Билдере же уже есть такая переменная и функция тоже есть, которая ее изменяет.
2) Зачем вообще этот exec(), если выполнение потока переходит во его внутреннюю очередь сообщений и там замирает, пока новое событие не появится. А что если надо чтобы поток постоянно что-то выполнял? Тогда получается все надо делать без exec().
3) Почему, когда в этой маленькой программе кнопкой [X] в заголовке закрываю окно с идикаторами прогресса, то окно родительское замерзает на определенное время (полагаю, на которое работает run() в потоке).

Пожалуйста, выскажите все свои замечания, идеи и рекомендации.

Если я тут написал какой-то бред, не ругайтесь, голова уже едет.

На всякий случай выложил исходники в сети: http://file.qip.ru/file/125946488/df6a8d23/ThreadedLoad2.html (http://file.qip.ru/file/125946488/df6a8d23/ThreadedLoad2.html)


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: alexman от Апрель 15, 2010, 20:32
Даже не читал текст (вернее прочитал только заголовок), но считаю, что нет смысла делать многопоточность для данной задачи, так как все скорее всего упрется в быстродействие жесткого диска. Многопоточность применяется, например, в вычеслительных задачах! Хотя если файлы на разных носителях, то смысл появляется...


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: Пантер от Апрель 15, 2010, 20:59
alexman, смысл есть, если не хочешь заморозки гуя.
По теме, сейчас влом читать, завтра/послезавтра гляну. kdm, у меня тут проектик был - PantherCommander, можешь найти его на форуме и посмотреть как я в потоке копирование делал. Код, конечно, УГ, но смысл понять, думаю, сможешь.


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: kdm от Апрель 15, 2010, 21:05
Пантер, О пасибо! Ща буду искать.
Во, нашел, буду разбираться.


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: alexman от Апрель 15, 2010, 21:34
Об потоке отдельном от гуя - это понятно! Имелось в виду, что не стоит создавать тучу потоков на чтение одного файла.


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: kdm от Апрель 15, 2010, 21:47
Ну я имел ввиду только один отделенный от гуи поток, который читает один файл, вернее один за другим стартуются, первый отработал, загрузил, потом за ним другой запускается. Вначале то я сделал один поток дополнительный который грузит все файлы по списку, но так как придется с загрузкой файлов еще потом работать в другой части прогрыммы, решил для обоих случаев сделать универсальный класс потока.
А в другой части программы хотел набор потоков того же класса, которые читают файлы с математическими данными одновременно и одновременно безопастно скидывают прочтенные данные в список данных, а потом вся эта муть рисуется в QGLWidget. Решил так делать потому что данные будут так же читаться не только из файлов,а еще из сокетов. Там я сделаю один сокет - один поток (для поставленной задачи сгодится). Поэтому и с файлами решил сделать аналогично.

Вообще, это дикая идея, но это единственное, что пришло мне в голову. Вразумите, пожалуйста, если я где-то дюже криво загнул.


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: Igors от Апрель 16, 2010, 17:34
Простой вариант приаттачен. Конечно, можно и по-другому. Напр. запускать нитку 1 раз и передать ей список файлов. Или сделать без флага mAborted, через сигнал


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: kdm от Апрель 16, 2010, 18:20
АаааАагромное спасибо :] ;)

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


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: Igors от Апрель 16, 2010, 18:39
Только одного не понял, зачем потоку exec, если при exec поток уходит в цикл обработки событий и там остается до появления нового события.
Запускать exec никто не заставляет, в данном примере его просто нет. А вообще может быть удобно - запустили несколько ниток и подкидываете им работенку по мере ее поступления используя обычные сигналы


Название: Re: Подскажите как сделать многопоточную загрузку из файлов
Отправлено: kdm от Апрель 16, 2010, 21:18
О, точно