Каталог статей

Главная » Статьи » Мои статьи

Сохранение отзывчивости графического интерфейса при выполнении длительных операций

Длительные операции в программе можно разделить на две категории. Первая — интенсивные вычислительные задачи, такие как: алгоритмы обработки аудио и видео данных, моделирование. Вторая — взаимодействие с медленными объектами «внешнего мира» программы(управление различным промышленным оборудованием, загрузка файлов по сети и проч.). Для второй категории задач проблема сохранения отзывчивости решается в Qt использованием асинхронных классов, т.е. классов, методы которых сразу после вызова возвращают управление, а о результате своей деятельности они оповещают соответствующим сигналом. Примером таких классов могут служить классы QFtp и QHttp.
В случае интенсивных вычислений (в не зависимости от использования механизма сигнал-слот) обработка событий прерывается, сетевое взаимодействие останавливается, таймеры замирают, программа не перерисовывается и перестает отвечать на ввод пользователя. Иными словами до окончания вычислений программа выглядит зависшей. Сохранить отзывчивость программы можно либо периодической обработкой ожидающих в очереди событий и тогда необходимые вычисления можно оставить в главном потоке, либо созданием дополнительного потока и переносом вычислительных алгоритмов в него. Создание многопоточных приложений требует высокой квалификации от программиста и несколько усложняет код. Для тех, кого не пугает усложнение исходного кода считаю необходимым напомнить первый закон творческого программирования: «Caveat emptor: стоимость сопровождения программного обеспечения пропорциональна квадрату творческих способностей программиста». Выбор пути решения зависит от задачи. Практически любую вычислительную задачу можно разбить на несколько подзадач, на несколько частей. Если эти части могут быть решены только последовательно, то велика вероятность того, что оптимальным вариантом будут вычисления в главном потоке. Ежели отдельные части независимы и могут вычисляться параллельно, то использование нескольких потоков может дать значительный прирост производительности.
Самое простое решение заключается в периодическом вызове QCoreApplication::processEvents() в коде вычислений. Эта функция обрабатывает ожидающие в очереди события. В случае, если действия пользователя нежелательны во время выполнения задачи можно отключить обработку событий мыши и клавиатуры заменив вызов QCoreApplication::processEvents() на QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents). С помощью QCoreApplication::processEvents() также можно «синхронизировать» асинхронные методы. Допустим у нас есть класс для общения с неким устройством. Ему можно послать команду с помощью метода send(QByteArray array) и получить ответ в слот data(QByteArray array) в течение некоторого времени. На случай отсутствия ответа предусмотрим таймаут. Если ответ не придет в течение 5 секунд, то ожидание мы прекращаем.

MyClass::say(QByteArray array)
{
QEventLoop::ProcessEventsFlags flags;
ready = false;
timeout = false;
flags |= QeventLoop::WaitForMoreEvents;
myTimer.start(5000);
send(array);
// Begin waiting
while ((ready == false) && (timeout == false))
{
QCoreApplication::processEvents(flags);
}
myTimer.stop;
if (ready == true)
{
//do somthing
}
else
{
//wtf
}
}
MyClass::timeout()
{
timeout = true;
}
MyClass::data(QByteArray array)
{
//some code
ready = true;
}

Если существует возможность разбить задачу на куски, то можно воспользоваться 0-миллисекундным таймером. Такой таймер срабатывает когда в очереди нет событий и приложение простаивает:
void MyClass::timeIsCome()
{
while ((part < partsNumber) && (!qApp->hasPendingEvents()))
{
doPart(part);
++
part;
}
}

В цикле выполняем части задачи пока не выполним все или пока не появятся ожидающие в очереди события. Альтернативой таймеру может служить метод QMetaObject::invokeMethod(). Для одного потока по-умолчанию используется прямой вызов, таким образом необходимо вызвать метод с явным указанием типа подключения Qt::QueuedConnection: QMetaObject::invokeMethod(this, "doPart", Qt::QueuedConnection). Подход аналогичен способу с таймер за тем исключением, что в слот мы можем передать аргумент. Недостаток этих подходов заключается в отсутствии информации когда и сколько приложение будет в состоянии ожидания.
Использование нескольких потоков — еще один вариант сохранения отзывчивости программы. Qt конечно же предоставляет поддержку потоков в виде кроссплатформенных потоковых классов. Поддерживается потокобезопасный способ отправки событий, между потоками возможно установка соединения сигнал-слот. Доступны низкоуровневые потоковые примитивы: мьютексы, блокировки чтение-запись, семафоры. В дополнении к этому в Qt 4.4 было добавлено пространство имен QtConcurrent содержащее высокоуровневое API для многопоточного программирования, которое включает управление асинхронными вычислениями и параллельную обработку списков. QtConcurrent берет на себя заботу о создании дополнительных потоков количество которых зависит от количества процессоров в системе на которой запускается приложение (см. QThread.idealThreadCount()).
Основные функции QtConcurrent :
* QtConcurrent::map() применяет функцию к каждому элементу контейнера, модифицируя элементы этого контейнера.
* QtConcurrent::mapped() подобна map(), но возвращает новый контейнер с измененными элементами.
* QtConcurrent::mappedReduced() подобна mapped(), но элементы сворачиваются в один результат.
* QtConcurrent::filter() Основываясь на результате функции-фильтра удаляет элементы контейнера. Если функция-фильтр возвращает true, то элемент остается в последовательности, иначе удаляется.
* QtConcurrent::filtered() подобна filter(), но отфильтрованный результат возвращается в новом контейнере.
* QtConcurrent::filteredReduced() подобна filtered(), но отфильтрованные элементы сворачиваются в один результат.
* QtConcurrent::run() запускает функцию в другом потоке.
* QFuture представляет результат асинхронного вычисления.
* QFutureIterator позволяет перемещаться по результатам доступным через QFuture.
* QFutureWatcher позволяет отслеживать QFuture через механизм сигнал-слот.
* QFutureSynchronizer класс для синхронизации нескольких QFuture.
Удобство использования QtConcurrent продемонстрируем на примере многопоточного экспорта html файлов в формат ODT (ISO/IEC 26300:2006 Open Document Format for Office Applications). В диалоге выбора файлов выбираем html файлы и в несколько потоков экспортируем их в Open Document Format. Процесс экспорта в любой момент можно приостановить или прервать. Сохранение в формат odt производит с помощью класса QTextDocumentWriter.
Листинг html2odf.h:

#ifndef WINDOW_H
#define WINDOW_H

#include <QtGui>

class Window : public QWidget
{
Q_OBJECT
public:
Window(QWidget *parent = 0);
~
Window();
public Q_SLOTS:
void open();
void showProgress(int num);
void savingResult(int index);
void finished();
private:
QStringList *files;
QPushButton *openButton;
QPushButton *cancelButton;
QPushButton *pauseButton;
QVBoxLayout *mainLayout;
QProgressBar *progress;
QHBoxLayout *progressLayout;
QFutureWatcher<bool> *odfSaving;
};

#endif // WINDOW_H

Листинг html2odf.cpp:
#include "html2odf.h"

bool save(const QString &fileName)
{
if (!QFile::exists(fileName))
return false;
QFile file(fileName);
if (!file.open(QFile::ReadOnly))
return false;

QByteArray data = file.readAll();
QTextCodec *codec = Qt::codecForHtml(data);
QString str = codec->toUnicode(data);
if (Qt::mightBeRichText(str))
{
QTextDocument d;
d.setHtml(str);
QTextDocumentWriter writer(fileName + ".odt", "odf");
return writer.write(&d);
}
else
{
return false;
}
}

Window::Window(QWidget *parent)
:
QWidget(parent)
{
setWindowTitle(tr("html2odf"));

files = new QStringList();

odfSaving = new QFutureWatcher<bool>(this);
connect(odfSaving, SIGNAL(progressValueChanged(int)), SLOT(showProgress(int)));
connect(odfSaving, SIGNAL(resultReadyAt(int)), SLOT(savingResult(int)));
connect(odfSaving, SIGNAL(finished()), SLOT(finished()));

openButton = new QPushButton(tr("Select HTML files"));
connect(openButton, SIGNAL(clicked()), SLOT(open()));

cancelButton = new QPushButton(tr("Cancel"));
cancelButton->setEnabled(false);
connect(cancelButton, SIGNAL(clicked()), odfSaving, SLOT(cancel()));

pauseButton = new QPushButton(tr("Pause/Resume"));
pauseButton->setEnabled(false);
connect(pauseButton, SIGNAL(clicked()), odfSaving, SLOT(togglePaused()));

QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addWidget(openButton);
buttonLayout->addWidget(cancelButton);
buttonLayout->addWidget(pauseButton);
buttonLayout->addStretch();

progressLayout = new QHBoxLayout();
progress = new QProgressBar();
progressLayout->addWidget(progress);

mainLayout = new QVBoxLayout();
mainLayout->addLayout(buttonLayout);
mainLayout->addLayout(progressLayout);
mainLayout->addStretch();
setLayout(mainLayout);
}

Window::~Window()
{
odfSaving->cancel();
odfSaving->waitForFinished();
}

void Window::open()
{
if (odfSaving->isRunning())
{
odfSaving->cancel();
odfSaving->waitForFinished();
}

*
files = QFileDialog::getOpenFileNames(this, tr("Select HTML files"),
QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation),
"*.htm *.html");

if (!files->isEmpty())
{
odfSaving->setFuture(QtConcurrent::mapped(*files, save));
progress->setMinimum(odfSaving->progressMinimum());
progress->setMaximum(odfSaving->progressMaximum());
openButton->setEnabled(false);
cancelButton->setEnabled(true);
pauseButton->setEnabled(true);
}
}

void Window::showProgress(int num)
{
progress->setValue(num);
}

void Window::savingResult(int index)
{
qDebug() << files->at(index) + ".odt" << " saved: " << odfSaving->resultAt(index) ;
}

void Window::finished()
{
openButton->setEnabled(true);
cancelButton->setEnabled(false);
pauseButton->setEnabled(false);
}


Источник: http://www.qtinfo.ru/stayingresponsive
Категория: Мои статьи | Добавил: qt-boy (09.08.2010)
Просмотров: 4951 | Комментарии: 2 | Теги: Сохранение отзывчивости графическог | Рейтинг: 0.0/0 |
Всего комментариев: 0
Имя *:
Email *:
Код *:
Форма входа
3D Облако
Мини-чат
Категории раздела
Мои статьи [19]
Облако
About Qt QList Qtractor 0.4.2 QListView git-репозиторий Qt Жасмин Бланшет QtCreator 1.1 Qt Extended Qt Solutions Динамическое приведение QObject библиотеки Qt 4.5.1 Пишем собственный веб броузер QFileDialog QMessageBox Добавление информации о версии в .e Вышли бета-версии библиотеки Qt 4.7 Qt qt quick qt sdk qtcreator Singleton для Qt QInputDialog QColorDialog Примеры использования класса QEvent QFontDialog QFileSystemWatcher следим за директ GIf Анимация в QLabel Открытие ссылки в браузере установл Открытие ссылки в браузере установл Добавление итемов в QTableWidget QMap итератор Помещаем окно посередине экрана Выставляем флаги для окон Отловить нажатие клавиши на QLabel Отловить нажатие клавиши на QLabel Определить какой именно контрол был Показать на время курсор с песочным Сделать окно модальным Зашифровать данные в MD5 или MD4 Удаление из QlistWidget строки Написание DLL на С++ Диалог открытия файла Работа с реестром Ограничение на ввод определенного к QMessageBox подтверждение удаления Многопоточный сервер на примере Чат Отправляем файл на PHP скрипт Пример пересылки от клиента к серве Алгоритм сортировки (не очень оптим но если не важна скорость то сойдет Вставка текста из буфера обмена(при Проверить существует ли файл Распечатка текста из QTextEdit Вывести текст из QTextEdit в диалог Интересное о QFileInfo Отловить событие закрытие окна Проверка веб ссылки на валидность Иконка для QPushButton Преобразовать QString в char* Выдрать IP и порт из строки Получение списка всех файлов в дире Окно поверх всех Ввод пароля в QLineEdit Ввод в QLineEdit только цифр и точк Скачать файл по Ftp Пример использования оператора fore Пример использования класса QDir Получить список папок Кодирование/Декодирвоание Base64 Получить переменные среды Оставить у диалога только кнопку за Запуск сторонней программы из Qt ис Drop Добавление эффекта выделяющего прям Получение информации о свободном пр Запись структуры(пользовательского Получение информации о свободном ди Простой пример использования QProce Как унаследоваться от QObject Отрисовать виджет по форме картинки Перемещение виджета вслед за мышкой Сделать диалог поверх всех окон/поз Повернуть изображение QSignalMapper - как пользоваться Qt приведение типа QFileSystemWatcher следим за директ Добавление сигнала clicked() в QLab Рекурсивный поиск файлов и папок QWheelEvent определить направление Отловить завершение работы системы Валидация на ввод только цифр styleSheet QLabel красим текст под styleSheet QLabel простой пример styleSheet QTableWidget красим заго styleSheet QTableWidget красим QTab QTableWidgetItem текст по центу Проверка сокета Выход из вложенных циклов styleSheet QPushButton выравнивание
Наш опрос
Оцените мой сайт
Всего ответов: 91
Поиск
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 2
    Гостей: 2
    Пользователей: 0