abclinuxu.cz AbcLinuxu.cz itbiz.cz ITBiz.cz HDmag.cz HDmag.cz abcprace.cz AbcPráce.cz
AbcLinuxu hledá autory!
Inzerujte na AbcPráce.cz od 950 Kč
Rozšířené hledání
×
    dnes 14:44 | IT novinky

    Mozilla představuje Firefox Enterprise určený jako podnikový prohlížeč pro organizace s přísnými požadavky na bezpečnost, správu a dodržování předpisů. S profesionální podporou. Dostupný je zatím ve Francii, Německu, Rakousku a Švýcarsku.

    Ladislav Hagara | Komentářů: 4
    dnes 14:33 | Bezpečnostní upozornění

    Kybernetický útok zasáhl nejméně 150 supermarketů Intermarché a několik prodejen řetězců Leclerc a Coopérative U. Chladicí jednotky napadených obchodních domů byly nastaveny na nepřetržité odmrazování a teplota v nich mnohdy vystouala až nad 50°C. Škody napáchané na chlazeném a mraženém zboží budou patrně dosahovat miliónů eur.

    … více »
    ❗Červivý Hřib krade❗ | Komentářů: 2
    dnes 04:00 | Komunita

    Stát Kalifornie zažaloval výrobce 3D tiskových filamentů Polymaker. Vrchní soud San Diego County tvrdí, že firma propagací svých výrobků mezi příznivci 3D tištěných zbraní podporovala výrobu takzvaných 'ghost guns' (DIY zbraní) a že její PLA filament pro 3D tiskárny lze za těchto okolností považovat za součást určenou k výrobě střelné zbraně. Žaloba požaduje mimo jiné finanční sankce a soudní zákaz činnosti, Polymaker se hájí tím, že filament je běžný a sám o sobě legální produkt. Celý text žaloby (PDF na Google Drive).

    ❗Červivý Hřib krade❗ | Komentářů: 18
    dnes 03:33 | Nová verze

    V červnu byla představena aplikace Myna pro převod řeči na text v prostředí desktopu Ubuntu. Včera byla Myna zpřístupněna širší komunitě Ubuntu formou technologické ukázky dostupné v Ubuntu 24.04 LTS, Ubuntu 26.04 LTS a Ubuntu 26.10 s GNOME na Waylandu.

    Ladislav Hagara | Komentářů: 0
    včera 21:11 | Komunita

    Ubuntu 27.04 bude Tenacious T-Rex.

    Ladislav Hagara | Komentářů: 0
    včera 20:44 | Nová verze

    Byla vydána nová verze 2026.4.0 "Never-slop" svobodného softwaru ScummVM (Wikipedie) umožňujícího bezproblémový běh mnoha klasických adventur na zařízeních, pro které nebyly nikdy určeny. Přehled novinek v poznámkách k vydání a na GitHubu. ScummVM oslavil 25 let. Verze 0.0.1 vyšla 8. října 2001.

    Ladislav Hagara | Komentářů: 0
    včera 17:22 | Humor

    Kanadský vývojář Anthony Sistilli vytvořil ironický DIY projekt parodující kamerové systémy Flock, s cílem názorně demonstrovat rizika masového sledování jaké provozují tyto komerční ALPR systémy. Z levné IP kamery a vlastního ML softwaru (napsaného s pomocí SI circa za 20 minut, kód se mi bohužel nepodařilo dohledat) běžícího v laptopu sestavil jednoduchý systém, který detekuje výhradně policejní vozidla, zaznamenává jejich SPZ a

    … více »
    ❗Červivý Hřib krade❗ | Komentářů: 7
    včera 12:44 | Upozornění

    Část údajů uživatelů internetového supermarketu Rohlík byla odcizena. Kyberútočníci napadli nástroj zákaznické podpory Rohlíku, odcizili tak jména, e-mailové adresy, v některých případech také telefonní čísla nebo přepisy komunikace mezi klienty a zákaznickou podporou. Rohlík incident nahlásil příslušným orgánům a přijal příslušné opatření, aby zamezil podobným případům v budoucnosti, uvedla firma.

    Ladislav Hagara | Komentářů: 16
    včera 11:11 | IT novinky

    Aplikace eDoklady fungovala v obou volebních dnech stabilně a prakticky bez front. Digitální a informační agentura (DIA) odhaduje, že se digitálním dokladem prokázalo více než 100.000 voličů. Aplikaci si před volbami či během nich aktualizovalo 247.517 uživatelů. Nových registrací bylo 19.431 a do aplikace se přihlásilo 357.879 lidí. Největší nápor přišel bezprostředně po otevření volebních místností mezi 14. a 16. hodinou, kdy si

    … více »
    Ladislav Hagara | Komentářů: 11
    včera 00:55 | Pozvánky

    Začal semestr, začal i podzim a kromě hub rostou jako po dešti i veřejné dílny v Praze. Nedávno strahováci referovali o hackerspacu Bordel, nyní se otevřela dílna Dejvice Lab jen kousek od ČVUT. Na blížící Virtuální Bastlírně tak uslyšíte nejen report z návštěvy dílny HWLab, ale i řadu novinek, událostí a úsměvných momentů, které se za poslední měsíc ve světě techniky, ale i vědy udály. Připojte se tedy již v úterý 13.10. od 20:00 a zapojte se do

    … více »
    bkralik | Komentářů: 1
    Které desktopové prostředí na Linuxu používáte?
     (9%)
     (7%)
     (4%)
     (21%)
     (29%)
     (8%)
     (5%)
     (2%)
     (14%)
     (20%)
    Celkem 2840 hlasů
     Komentářů: 32, poslední 11.10. 17:02
    Rozcestník

    Grafické programy v Qt 4 – 11 (vláknování s QThreadPool)

    18. 8. 2010 | David Watzke | Programování | 6796×

    Hlavním tématem tohoto dílu je implementace vláknování s využitím objektů QThreadPool a QRunnable. Součástí článku je plnohodnotný program s grafickým uživatelským rozhraním využívající QThreadPool pro paralelizaci zadaných příkazů.

    K čemu je QThreadPool a QRunnable?

    QThreadPool je třída, která spravuje vlákna (QThread). Je součástí Qt od verze 4.4. Pokud váš program používá vlákna a často vytváří a ničí jejich instance, je vhodné použít QThreadPool, který umí efektivně znovupoužít objekty QThread. Jak se používá? Nejdříve si musíte vytvořit novou třídu založenou na QRunnable, což je obyčejná třída, která v základu ani není QObject (ale může být, chcete-li z ní třeba vysílat signály). Této třídě je třeba implementovat chráněnou (protected) metodu void run() tak, jak to znáte, pokud jste již používali QThread (viz článek Grafické programy v Qt 4 – 5 (regexpy, vlákna a ukazatel průběhu)). Zkrátka tato metoda obsahuje příkazy, které se budou provádět v jednom vlákně.

    class Runnable : public QRunnable
    {
    public:
    	Runnable() {}
    	~Runnable() {}
    
    protected:
    	void run()
    	{
    		qDebug() << "Pozdrav z vlákna" << QThread::currentThread();
    	}
    }
    

    Jakmile máte implementovanou třídu založenou na QRunnable, můžete začít spouštět vlákna. Každý Qt program má jednu globální instanci QThreadPool. Ukazatel na tuto instanci vám vrátí statická metoda QThreadPool::globalInstance(). Použití (z funkce) potom vypadá takto:

    Runnable *runnable = new Runnable();
    
    /* spustí se okamžitě, pokud není překročen počet běžících vláken
     * QThreadPool::maxThreadCount() a pokud je, zařadí se příkaz do fronty
     * a spustí se až nějaké vlákno skončí */
    
    QThreadPool::globalInstance()->start(runnable);
    
    Runnable *run2 = new Runnable();
    
    /* pokusí se spustit hned a když to vyjde, vrátí true
     * ale pokud to nejde, ihned vrátí false a nepřidá runnable do fronty */
    
    bool started = QThreadPool::globalInstance()->tryStart(run2);
    
    if(!started)
    	qDebug() << "run2 se nespustil";
    

    Jednu instanci Runnable lze spustit i víckrát než jednou. Ve výchozím režimu je povolené automatické ničení objektů Runnable ihned poté, co skončí vykonávání metody run(), takže jediná možnost je přímo z této metody zavolat QThreadPool::globalInstance->tryStart(this). Ovšem když automatické ničení objektů zakážete pomocí metody QRunnable::setAutoDelete(false) (a budete se o něj starat sami, jinak dojde k únikům paměti), tak se vás toto omezení netýká a můžete stejnou instanci pouštět znova a znova, jak se vám bude chtít.

    Ještě se sluší zmínit vychytávky jako podpora priorit a rezervování vláken. Spouštíte-li runnable přes QThreadPool::start(), můžete uvést i prioritu (integer), podle které se příkaz zařadí do fronty dolů nebo nahoru. Větší číslo symbolizuje větší prioritu.

    QThreadPool->globalInstance->start(runnable, 10);
    

    Rezervování vlákna se hodí, pokud nutně potřebujete vlákno pro zvláštní účely a nezáleží vám na tom, že se může dočasně přesáhnout předem nastavený maximální počet vláken. Toto se provádí metodou QThreadPool::reserveThread() a je to také důvod, proč QThreadPool::activeThreadCount() (počet aktivních vláken) může převýšit hodnotu QThreadPool::maxThreadCount() (maximální počet vláken). Poté co s rezervovaným vláknem skončíte, zavolejte QThreadPool::releaseThread(), aby mohlo být vlákno dále běžně používáno.

    Program QtJobs

    Program QtJobs jsem začal psát proto, abych si vyzkoušel práci s threadpool a zároveň aby rovnou vzniklo něco trochu užitečného. Na ukázkový program je sice docela velký, ale aspoň je na něm vidět použití spousty věcí, které jsem zmiňoval v minulých dílech (hlavní okno programu, nastavení, Qt Designer, regulární výrazy, vlákna a ukazatel průběhu, externí procesy, taby, modální okna a lokalizace). Uživatelské rozhraní vypadá takto:

    qt qt qt

    Program slouží k paralelnímu spouštění příkazů v tolika vláknech, kolik je ideální pro váš systém (případně si můžete zvolit vlastní počet vláken). Znáte-li program xjobs, tak můj program by se dal považovat za něco podobného s GUI. Představte si, že chcete dekódovat MP3 (do WAV) pomocí lame --decode, které pracuje v jednom vlákně. Zkusil jsem takto dekódovat jedno album a trvalo to na mém dvoujádrovém CPU s HT skoro 38 sekund. Když jsem použil QtJobs, který dle počtu logických procesorů zvolil 4 vlákna, stejného výsledku jsem docílil za 20 sekund.

    Použití je vcelku jednoduché. Na prvním tabu je seznam souborů, do kterého přidáte vstupní soubory (v našem případě by to byly MP3 soubory). Na dalším tabu nastavíte příkaz, který se na soubory bude pouštět a název výstupních souborů (změnu přípony, výstupní adresář, atp.). Do příkazu nezapomeňte uvést proměnnou $FILE, která bude nahrazena za název vstupního souboru, a proměnnou $OFILE, která bude nahrazena za název výstupního souboru. Použití proměnné $OFILE je volitelné, ale má-li se projevit nastavení výstupního souboru, je třeba ji uvést. Příkaz pro dekódování MP3 by tedy vypadal takto:

    lame --decode $FILE $OFILE
    

    Program podporuje profily, takže nemusíte stále dokola vyplňovat to samé, stačí to udělat jednou a profil uložit například s názvem „Dekódování MP3“ a příště jej jen načíst. Když máte vše nastavené, stisknete tlačítko „Spustit“, čímž to celé odstartujete. Program vás přepne na třetí tab, kde je ukazatel průběhu, možnost příkazy zastavit či zabít, a také seznam běžících příkazů, seznam úspěšně zpracovaných souborů a seznam příkazů, které skončily s nenulovou návratovou hodnotou.

    A následuje okomentovaný kód, který uvolňuji pod GPLv3. Celý projekt si můžete stáhnout přes Git pomocí příkazu:

    git clone git://repo.or.cz/qtjobs.git
    

    Zkompilujete jej jako vždy spuštěním příkazu qmake && make v adresáři s kódem. Lokalizaci sestavíte příkazem: lrelease qtjobs.pro. Aktuální i starší verze zdrojáků si můžete prohlédnout přes gitweb.

    main.cpp: Nic nového: Nastavíme název programu a doménu (či organizaci) pro QSettings, načteme případnou lokalizaci, zobrazíme hlavní okno a spustíme smyčku událostí.

    #include <QtGui/QApplication>
    #include <QtCore/QTextCodec>
    #include <QtCore/QLocale>
    #include <QtCore/QTranslator>
    #include "mainwindow.h"
    
    int main(int argc, char *argv[])
    {
    	QApplication app(argc, argv);
    
    	// údaje pro QSettings (konfigurák)
    	app.setApplicationName("qtjobs");
    	app.setOrganizationDomain("watzke.cz");
    
    	// nastavíme kódování pro lokalizaci a řetězce C
    	{
    	QTextCodec *utf8 = QTextCodec::codecForName("UTF-8");
    	QTextCodec::setCodecForCStrings(utf8);
    	QTextCodec::setCodecForTr(utf8);
    	}
    
    	// načteme lokalizaci
    	QTranslator tr;
    	tr.load(app.applicationName() + "_" + QLocale::system().name());
    	app.installTranslator(&tr);
    
    	// zobrazíme hlavní okno
    	MainWindow wnd;
    	wnd.show();
    
    	// spustíme smyčku událostí
    	return app.exec();
    }
    

    mainwindow.h: API.

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QList>
    #include <QMainWindow>
    #include <QSettings>
    #include <QDebug>
    
    #include "runnable.h"
    
    namespace Ui
    {
    	class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
    	Q_OBJECT
    public:
    	MainWindow(QWidget *parent = 0);
    	~MainWindow();
    
    //	friend class Runnable;
    
    protected:
    	void changeEvent(QEvent *e);
    
    private:
    	Ui::MainWindow *ui;
    	QSettings m_profiles;
    	QList<Runnable*> m_runnables;
    	bool m_ok;
    
    	void loadProfiles(bool load_default = true);
    	void checkProcessing();
    
    private slots:
    	void addFiles();
    	void forceExtensionChange(bool force);
    	void removeSelectedFiles();
    
    	void removeSelectedProfile();
    	void loadSelectedProfile();
    	void saveProfile();
    
    	void runJobs();
    	void stopJobs();
    	void killJobs();
    
    	void setGuiProcessingState(bool processing);
    
    	void removeRunnable(Runnable* runnable, bool ok = true);
    
    	void startedJob(QString cmd);
    	void doneJob(QString cmd, QString file, int retcode);
    	void failedJob(QString error);
    };
    
    #endif // MAINWINDOW_H
    

    mainwindow.cpp: Implementace hlavního okna. Hlavní okno v případě tohoto programu dělá vše kromě samotného spouštění příkazů, které probíhá ve vláknech voláním Runnable::run(). Uživatelské rozhraní je navržené v Qt Designeru, čímž odpadá velká část nudného rutinního kódu.

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    #include <QtGui>
    #include <QThreadPool>
    
    MainWindow::MainWindow(QWidget *parent) :
    	QMainWindow(parent),
    	ui(new Ui::MainWindow), m_ok(true)
    {
    	// načte navržené GUI
    	ui->setupUi(this);
    
    	// aktivuje první tab
    	ui->tabWidget->setCurrentIndex(0);
    
    	// nastaví počet ideální vláken do spinboxu
    	ui->threadBox->setValue(QThread::idealThreadCount());
    
    	/* za jak dlouho se vlákno zničí při nečinnosti…
    		výchozích 30 s je moc, protože pak se může stát, že
    		program nebude respektovat nastavení počtu vláken */
    	QThreadPool::globalInstance()->setExpiryTimeout(100);
    
    	// načte profily
    	loadProfiles();
    }
    
    /* nechá uživatele vybrat soubory přes souborový dialog a
    	přidá je do seznamu */
    void MainWindow::addFiles()
    {
    	QStringList files = QFileDialog::getOpenFileNames(this,
    				tr("Select files to be processed"),
    				QDir::homePath());
    	ui->fileList->addItems(files);
    }
    
    /* odstraní vybrané soubory ze seznamu */
    void MainWindow::removeSelectedFiles()
    {
    	QList<QListWidgetItem*> items = ui->fileList->selectedItems();
    
    	ui->fileList->setUpdatesEnabled(false);
    
    	while(!items.isEmpty())
    		delete items.takeLast();
    
    	ui->fileList->setUpdatesEnabled(true);
    }
    
    /* načte profily a případně vytvoří výchozí (prázdný) profil */
    void MainWindow::loadProfiles(bool load_default)
    {
    	// ujistíme se, že je comboBox prázdný (důležité pro znovunačtení)
    	while(ui->comboProfile->count())
    		ui->comboProfile->removeItem(0);
    
    	QStringList profiles = m_profiles.childGroups();
    
    	// vytvoříme výchozí (prázdný) profil, pokud žádný neexistuje
    	if(!profiles.contains("default"))
    	{
    		m_profiles.beginGroup("default");
    		m_profiles.setValue("name", tr("Default"));
    		m_profiles.setValue("command", QString());
    		m_profiles.setValue("extension", QString("ext"));
    		m_profiles.setValue("outdir", QDir::homePath());
    		m_profiles.setValue("samedir", false);
    		m_profiles.setValue("want_extension", false);
    		m_profiles.setValue("autothread", true);
    		m_profiles.setValue("threadcount", QThread::idealThreadCount());
    		m_profiles.endGroup();
    
    		profiles << "default";
    	}
    
    	// přidáme dostupné profily do comboBoxu
    	foreach(QString profile, profiles)
    	{
    		m_profiles.beginGroup(profile);
    		QString name = m_profiles.value("name").toString();
    		m_profiles.endGroup();
    
    		if(name.isEmpty())
    			continue;
    
    		//qDebug() << "adding profile" << profile << "as" << name;
    
    		ui->comboProfile->addItem(name, profile);
    	}
    
    	if(load_default)
    	{
    		// nalistujeme v comboBoxu výchozí profil
    		int index = ui->comboProfile->findData("default");
    		ui->comboProfile->setCurrentIndex(index);
    
    		// načteme jej
    		loadSelectedProfile();
    	}
    }
    
    /* odstraní vybraný profil */
    void MainWindow::removeSelectedProfile()
    {
    	// zjistíme index vybraného profilu
    	int index = ui->comboProfile->currentIndex();
    	// a jeho název
    	QString profile = ui->comboProfile->itemData(index).toString();
    
    	if(profile.isEmpty())
    		return;
    
    	// nedovolíme smazat výchozí profil
    	if(profile == "default")
    	{
    		QMessageBox::critical(this, tr("Error!"),
    				      tr("You cannot remove the default profile!"));
    		return;
    	}
    
    	// odstraníme z comboBoxu
    	ui->comboProfile->removeItem(index);
    
    	// nalistujeme výchozí profil
    	index = ui->comboProfile->findData("default");
    	ui->comboProfile->setCurrentIndex(index);
    
    	// odstraníme z konfiguráku
    	m_profiles.remove(profile);
    }
    
    /* načte vybraný profil */
    void MainWindow::loadSelectedProfile()
    {
    	// zjistíme index vybraného profilu
    	int index = ui->comboProfile->currentIndex();
    	// a jeho název
    	QString profile = ui->comboProfile->itemData(index).toString();
    
    	// touhle dobou už bychom měli znát název profilu (pro načtení)
    	Q_ASSERT(!profile.isEmpty());
    
    	// načteme proměnné z nastavení
    	m_profiles.beginGroup(profile);
    	QString command = m_profiles.value("command").toString();
    	QString extension = m_profiles.value("extension", "ext").toString();
    	QString outdir = m_profiles.value("outdir", QDir::homePath()).toString();
    	bool samedir = m_profiles.value("samedir", false).toBool();
    	bool want_extension = m_profiles.value("want_extension", false).toBool();
    	bool autothread = m_profiles.value("autothread", true).toBool();
    	int threadcount = m_profiles.value("threadcount", QThread::idealThreadCount()).toInt();
    	m_profiles.endGroup();
    
    	// nastavíme načtené nastavení:
    	// 1) příkaz
    	ui->commandEdit->setText(command);
    
    	// 2) příponu
    	if(!want_extension)
    		ui->extCb->setChecked(false);
    	else
    	{
    		ui->extCb->setChecked(true);
    		ui->extEdit->setText(extension);
    	}
    
    	// 3) výstupní adresář
    	if(samedir)
    		ui->radioSame->setChecked(true);
    	else
    	{
    		ui->radioOther->setChecked(true);
    		ui->outDirEdit->setText(outdir);
    	}
    
    	// 4) nastavení vláken
    	ui->threadcountCb->setChecked(!autothread);
    	ui->threadBox->setValue(threadcount);
    }
    
    /* uloží profil */
    void MainWindow::saveProfile()
    {
    	// název profilu načteme z patřičného textového pole
    	QString profile = ui->profileEdit->text();
    
    	// pokud název nebyl zadán, přepíšeme aktuálně nalistovaný profil
    	if(profile.isEmpty())
    	{
    		int index = ui->comboProfile->currentIndex();
    		profile = ui->comboProfile->itemData(index).toString();
    	}
    
    	// touhle dobou už bychom rozhodně měli znát název profilu (pro uložení)
    	Q_ASSERT(!profile.isEmpty());
    
    	// uložíme požadované hodnoty
    	m_profiles.beginGroup(profile);
    	m_profiles.setValue("name", profile);
    	m_profiles.setValue("command", ui->commandEdit->text());
    	m_profiles.setValue("extension", ui->extEdit->text());
    	m_profiles.setValue("outdir", ui->outDirEdit->text());
    	m_profiles.setValue("samedir", ui->radioSame->isChecked());
    	m_profiles.setValue("want_extension", ui->extCb->isChecked());
    	m_profiles.setValue("autothread", !ui->threadcountCb->isChecked());
    	m_profiles.setValue("threadcount", ui->threadBox->value());
    	m_profiles.endGroup();
    
    	// znovu načíst seznam profilů
    	loadProfiles(false);
    
    	// vybrat nově uložený profil
    	int index = ui->comboProfile->findData(profile);
    	ui->comboProfile->setCurrentIndex(index);
    
    	// načíst nově uložený profil
    	loadSelectedProfile();
    
    	// vyprázdnit pole s názvem pro nový profil
    	ui->profileEdit->setText("");
    }
    
    /* spustí příkazy */
    void MainWindow::runJobs()
    {
    	// obnovit stav widgetů z minulého běhu
    	ui->commandList->clear();
    	ui->processedList->clear();
    	ui->errorList->clear();
    	ui->progressBar->reset();
    
    	// vytvoříme seznam názvů souborů
    	QStringList fileList;
    	int count = ui->fileList->count();
    
    	for(int i=0; i < count; ++i)
    		fileList << ui->fileList->item(i)->text();
    
    	// kontrola, zda byly zadány nějaké soubory
    	if(count < 1)
    	{
    		QMessageBox::critical(this, tr("Error!"),
    			tr("Please add some files to the file list on the \"File "
    			   "list\" tab."));
    		return;
    	}
    
    	// je zadána přípona?
    	if(ui->extCb->isChecked() && ui->extEdit->text().isEmpty())
    	{
    		QMessageBox::critical(this, tr("Error!"),
    			tr("Please either enter an extension or uncheck the option "
    			   "for it's changing."));
    		return;
    	}
    
    	// byl zadán platný výstupní adresář?
    	if(ui->radioOther->isChecked())
    	{
    		QFileInfo dirInfo(ui->outDirEdit->text());
    		if(!dirInfo.isDir() || !dirInfo.isWritable())
    		{
    			QMessageBox::critical(this, tr("Error!"),
    				tr("Please select a valid writable output directory."));
    			return;
    		}
    	}
    
    	// obsahuje příkaz proměnnou $FILE?
    	if(!ui->commandEdit->text().contains("$FILE"))
    	{
    		QMessageBox::critical(this, tr("Error!"),
    			tr("The command must contain the $FILE variable (and should "
    			   "also contain the optional $OFILE variable)."));
    		return;
    	}
    
    	// nastavíme ukazatel průběhu
    	ui->progressBar->setRange(0, count);
    	ui->progressBar->setFormat("%v/%m (%p%)");
    	ui->progressBar->setValue(0);
    
    	// nastavíme počet vláken
    	int threadcount = QThread::idealThreadCount();
    
    	if(ui->threadcountCb->isChecked())
    		threadcount = ui->threadBox->value();
    
    	QThreadPool::globalInstance()->setMaxThreadCount(threadcount);
    
    	// přepneme GUI do stavu zpracovávání
    	setGuiProcessingState(true);
    
    	// přidáme všechny příkazy do fronty…
    	foreach(QString fileName, fileList)
    	{
    		// naše struktura s nastavením
    		RunnableSettings s;
    
    		// příkaz
    		s.command = ui->commandEdit->text();
    		// přípona
    		s.extension = ui->extEdit->text();
    		// výstupní adresář
    		s.outdir = ui->outDirEdit->text();
    		// vstupní soubor
    		s.infile = fileName;
    		// chceme příponu?
    		s.bExtension = ui->extCb->isChecked();
    		// chceme ukládat do adresáře, kde je uložen vstupní soubor?
    		s.bSameDir = ui->radioSame->isChecked();
    
    		Runnable *runnable = new Runnable(s);
    		// propojíme signály
    		connect(runnable, SIGNAL(destroyMe(Runnable*,bool)),
    			this, SLOT(removeRunnable(Runnable*,bool)));
    		connect(runnable, SIGNAL(started(QString)),
    			this, SLOT(startedJob(QString)));
    		connect(runnable, SIGNAL(done(QString,QString,int)),
    			this, SLOT(doneJob(QString,QString,int)));
    		connect(runnable, SIGNAL(failed(QString)),
    			this, SLOT(failedJob(QString)));
    
    		// přidáme objekt do seznamu
    		m_runnables << runnable;
    
    		// a přidáme job do threadpoolu
    		QThreadPool::globalInstance()->start(runnable);
    	}
    
    	// přepnout na tab s průběhem
    	ui->tabWidget->setCurrentIndex(2);
    }
    
    /* zastaví příkazy */
    void MainWindow::stopJobs()
    {
    	foreach(Runnable *runnable, m_runnables)
    		runnable->stop();
    }
    
    /* zabije příkazy */
    void MainWindow::killJobs()
    {
    	foreach(Runnable *runnable, m_runnables)
    		runnable->kill();
    }
    
    /* odstraní dokončený příkaz (instanci Runnable) */
    void MainWindow::removeRunnable(Runnable *runnable, bool ok)
    {
    	m_runnables.removeAll(runnable);
    	delete runnable;
    
    	if(!ok)
    		m_ok = false;
    
    	checkProcessing();
    }
    
    /* slot pro „příkaz byl spuštěn“ */
    void MainWindow::startedJob(QString cmd)
    {
    	// přidá příkaz do seznamu příkazů
    	new QListWidgetItem(cmd, ui->commandList);
    }
    
    /* slot pro „příkaz byl dokončen“ (s názvem souboru a návratovou hodnotou) */
    void MainWindow::doneJob(QString cmd, QString file, int retcode)
    {
    	// aktualizujeme hodnotu v ukazateli průběhu
    	int done = ui->progressBar->value()+1;
    	ui->progressBar->setValue(done);
    
    	/* podle návratové hodnoty přidáme soubor do seznamu úspěšně zpracovaných
    	   nebo přidáme příkaz do seznamu chyb */
    	if(!retcode)
    		ui->processedList->addItem(file);
    	else
    	{
    		QString error = QString(tr("command returned %1: %2")).arg(retcode).arg(cmd);
    		ui->errorList->addItem(error);
    	}
    
    	// najdeme v seznamu běžících příkazů ten, který právě skončil
    	QList<QListWidgetItem*> item = ui->commandList->findItems(cmd,
    						Qt::MatchExactly|Qt::MatchCaseSensitive);
    
    	//Q_ASSERT(item.count() == 1);
    
    	// ověříme, zda jsme nalezli jen 1
    	if(item.count() != 1)
    	{
    		qDebug() << "WARNING: removeCommandFromList() found >1 item."
    			 << "This shouldn't happen. Items:";
    		foreach(QListWidgetItem* it, item)
    			qDebug() << "> " << it->text();
    	}
    
    	// a smažeme jej
    	if(item.count() > 0)
    		delete item.at(0);
    	else
    		qDebug() << "BUG: removeCommandFromList() found no items."
    			 << "This should never happen.";
    }
    
    /* slot pro „příkaz selhal“ */
    void MainWindow::failedJob(QString error)
    {
    	// aktualizujeme hodnotu v ukazateli průběhu
    	int done = ui->progressBar->value()+1;
    	ui->progressBar->setValue(done);
    
    	// přidáme do seznamu chyb
    	ui->errorList->addItem(error);
    }
    
    /* přepne GUI do stavu zpracovávání (nebo naopak) */
    void MainWindow::setGuiProcessingState(bool processing)
    {
    	ui->runBtn->setDisabled(processing);
    	ui->stopBtn->setEnabled(processing);
    	ui->killBtn->setEnabled(processing);
    
    	if(processing)
    		m_ok = true;
    }
    
    /* zkontroluje, zda už je vše hotovo a ukáže patřičnou zprávu */
    void MainWindow::checkProcessing()
    {
    	// pokud v seznamu jsou ještě úlohy, tak hned vrátíme
    	if(m_runnables.count())
    		return;
    
    	// zdá se, že máme hotovo
    
    	if(QThreadPool::globalInstance()->activeThreadCount() != 0)
    		qDebug() << "looks like processing is done but"
    			 << "QThreadPool::activeThreadCount() != 0";
    
    	// vrátíme GUI do klidového stavu
    	setGuiProcessingState(false);
    
    	// vyprázdníme seznam příkazů (pro jistotu)
    	ui->commandList->clear();
    	//ui->processedList->clear();
    
    	// hotovo bez chyb
    	if(m_ok && !ui->errorList->count())
    		QMessageBox::information(this, tr("Done!"),
    			tr("Done! All the files were successfully processed!"));
    	else if(m_ok)
    	{ // hotovo s chybami
    		QMessageBox::StandardButton btn;
    		btn = QMessageBox::warning(this, tr("Done with errors!"),
    				tr("Done with errors! See the list of the failed "
    				   "commands on the \"Progress\" tab."));
    		if(btn == QMessageBox::Ok)
    			ui->tabWidget->setCurrentIndex(2);
    	} // přerušeno uživatelem
    	else
    		QMessageBox::critical(this, tr("Interrupted!"),
    			tr("Processing was interrupted by pressing the Stop or "
    			   "the KILL button."));
    
    	// resetujeme ukazatel průběhu
    	ui->progressBar->reset();
    }
    
    /* vynutí změnu přípony
    	-> hodí se (nejen), když výstupní soubory ukládáte do stejného adresáře */
    void MainWindow::forceExtensionChange(bool force)
    {
    	// pokud změnu přípony chceme vynutit, tak zaškrtneme příslušné pole
    	if(force)
    		ui->extCb->setChecked(true);
    
    	// povolí nebo zakáže změnu zaškrtávacího pole
    	ui->extCb->setDisabled(force);
    }
    
    /* výchozí destruktor */
    MainWindow::~MainWindow()
    {
    	delete ui;
    }
    
    /* tuhle funkci vygeneroval Qt Creator sám;
     umožňuje měnit lokalizaci za běhu… */
    void MainWindow::changeEvent(QEvent *e)
    {
    	QMainWindow::changeEvent(e);
    
    	switch(e->type())
    	{
    	case QEvent::LanguageChange:
    		ui->retranslateUi(this);
    		break;
    	default:
    		break;
    	}
    }
    

    runnable.h: API. Zde je jedna malá zvláštnost: Objekt zakládáme nejen na QRunnable, ale i na QObject, protože potřebujeme vysílat signály hlavnímu oknu. Zasahovat do uživatelského rozhraní z jiného vlákna, než je to hlavní (tzv. GUI vlákno), není příliš moudré: V lepších případech se projeví varováním na výstupu, ovšem častěji spíše pádem programu. To je důvod, proč komunikujeme pomocí signálů.

    #ifndef RUNNABLE_H
    #define RUNNABLE_H
    
    #include <QObject>
    #include <QRunnable>
    #include <QString>
    
    // struktura s informacemi pro Runnable
    struct RunnableSettings
    {
    	QString command;
    	QString extension;
    	QString outdir;
    	QString infile;
    	//QString outfile;
    	bool bExtension;
    	bool bSameDir;
    };
    
    // chceme ze třídy vysílat signály, proto ji zakládáme i na QObjectu
    class Runnable : public QObject, public QRunnable
    {
    	// nezapomeneme na makro Q_OBJECT
    	Q_OBJECT
    public:
    	Runnable(const RunnableSettings &s);
    
    	void stop() { m_stop = true; }
    	void kill() { stop(); m_kill = true; }
    
    protected:
    	void run();
    
    private:
    	QString m_command;
    	QString m_infile;
    
    	bool m_stop;
    	bool m_kill;
    
    signals:
    	void started(QString);
    	void done(QString,QString,int);
    	void failed(QString);
    	void destroyMe(Runnable*,bool);
    };
    
    #endif // RUNNABLE_H
    

    runnable.cpp: Implementace.

    #include "runnable.h"
    
    #include <QDebug>
    #include <QDir>
    #include <QFileInfo>
    #include <QProcess>
    #include <QRegExp>
    
    Runnable::Runnable(const RunnableSettings &s)
    	: m_stop(false), m_kill(false)
    {
    	/* zakážeme automatické ničení objektů Runnable, protože se o
    		jejich správu staráme sami */
    	setAutoDelete(false);
    
    	// uložíme příkaz a název vstupního souboru do členské proměnné
    	m_infile = s.infile;
    	m_command = s.command;
    
    	// sestavíme cestu k výstupnímu souboru…
    	QFileInfo fileInfo(m_infile);
    	QString outfile;
    
    	// adresář (stejný nebo zadaný?)
    	if(s.bSameDir)
    		outfile = fileInfo.dir().canonicalPath() + "/";
    	else
    		outfile = s.outdir + "/";
    
    	// soubor
    	QString fileName = fileInfo.fileName();
    
    	// změna přípony
    	if(s.bExtension)
    	{
    		QRegExp regex("^(.*)\\.(\\w+)$", Qt::CaseInsensitive,
    						 QRegExp::RegExp2);
    		fileName.replace(regex, "\\1." + s.extension);
    	}
    
    	outfile += fileName;
    
    	// nahradíme proměnné $FILE a $OFILE
    	m_command.replace("$FILE", "\""+m_infile+"\"");
    	m_command.replace("$OFILE", "\""+outfile+"\"");
    }
    
    void Runnable::run()
    {
    	// pokud uživatel stiskl „Zastavit“ či „ZABÍT“, tak ani nezačínáme
    	if(m_stop)
    	{
    		//qDebug() << "user pressed Stop or KILL, so not even starting";
    		emit destroyMe(this, false);
    		return;
    	}
    
    	//qDebug() << this << "running" << m_command;
    
    	QProcess process;
    
    	// spustíme příkaz
    	process.start(m_command, QIODevice::ReadWrite|QIODevice::Text);
    
    	// počkáme, až se spustí
    	if(process.waitForStarted(1000))
    	{
    		// vyšleme signál, že příkaz byl spuštěn
    		emit started(m_command);
    	}
    	else
    	{ // pokud spouštění příkazu selhalo
    		// chybová hláška
    		QString errorString = tr("command failed to start");
    
    		if(process.error() != QProcess::FailedToStart)
    			errorString = tr("command failed due to an unknown error");
    
    		// vyšleme signál, že příkaz selhal
    		emit failed(errorString + ": " + m_command);
    
    		/*process.kill();
    		// počkáme na zabití procesu, maximálně 1 s
    		process.waitForFinished(1000);*/
    
    		emit destroyMe(this, true);
    
    		return;
    	}
    
    	// smyčka kontrolující, zda uživatel stiskl „ZABÍT“
    	while(m_kill || !process.waitForFinished(200))
    	{
    		// pokud ne, dále čekáme na dokončení procesu
    		if(!m_kill)
    			continue;
    
    		qDebug() << "killing" << m_command;
    
    		// zabijeme proces
    		process.kill();
    
    		break;
    	}
    
    	// počkáme na zabití procesu, maximálně 1 s
    	process.waitForFinished(1000);
    
    	// příkaz doběhl
    	emit done(m_command, m_infile, m_kill ? 137 : process.exitCode());
    	// vyžadáme zničení objektu
    	emit destroyMe(this, !m_kill);
    }
    

    Není-li vám něco jasné, určitě se zeptejte v diskusi. Zrovna tak tam napište, když v programu najdete chybu; hodně jsem jich opravil při testování, ale jsem jen člověk, mohl jsem něco přehlédnout.

           

    Hodnocení: 100 %

            špatné • dobré        

    Nástroje: Tisk bez diskuse

    Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

    Komentáře

    Vložit další komentář

    18.8.2010 11:32 JoHnY2
    Rozbalit Rozbalit vše Re: Grafické programy v Qt 4 – 11 (vláknování s QThreadPool)
    Dik za dalsi clanek o Qt. Diky tem predchozim jsem udelal semestralku z C++ka :-).
    18.8.2010 21:44 Xwinus
    Rozbalit Rozbalit vše Re: Grafické programy v Qt 4 – 11 (vláknování s QThreadPool)
    Díky za super sérii článků, která mě přivedla ke Qt. Doufám že tento není poslední.
    20.8.2010 23:57 kralyk z abclinuxu | skóre: 29 | blog:  
    Rozbalit Rozbalit vše Re: Grafické programy v Qt 4 – 11 (vláknování s QThreadPool)
    Pěknej článek. Líbí se mi terminus technicus "vláknování" ;-)

    (Teda on by ten prográmek šel řešit celkem dobře i bez ThreadPoolu, no ale samozřejmě chápu, že na tom by se špatně vysvětloval ThreadPool ;-))
    27.8.2010 11:55 smoegel
    Rozbalit Rozbalit vše Re: Grafické programy v Qt 4 – 11 (vláknování s QThreadPool)
    Velmi uzitocne :-) Dakujem za namahu a ochotu vysvetlovat pre niekoho mozno trivialne veci. Pre prilezitostneho programatora ako som ja je to 100x nazornejsie ako dokumentacia QT.

    este raz dik

    Založit nové vlákno • Nahoru

    ISSN 1214-1267   www.czech-server.cz
    © 1999-2015 Nitemedia s. r. o. Všechna práva vyhrazena.