Briefly
Qter often encounter problems with GUI blocking due to time-consuming operations. In fact, this problem is not difficult to overcome, can be used in many different ways, below I will list some of the optional range, according to the use of processing.
- Briefly
- Perform time-consuming operations
- Manual Event handling
- Use a worker thread
- Wait for local event loop
- To solve the problem gradually
- Parallel programming
- Summarize
- More references
Perform time-consuming operations
The first thing we need to do is to determine the scope of the problem to be solved. The above problems may appear in two forms.
When a program performs computationally intensive tasks, in order to obtain a series of sequential operations the final result. An example of such a task is to compute a fast Fourier transform.
When a program triggers some behavior (for example: Network download) and waits for it to complete, proceed to the next step of the algorithm. This problem is easy to avoid when using QT, as most of the asynchronous tasks performed by the framework will be signaled after completion, and can be connected to the continuation algorithm on the slot function.
All event handling is stopped during the calculation (ignoring any signal slots used). The result: The GUI is not refreshed, user input is not processed, network activity is stopped-the application appears to be blocked. In fact, irrelevant portions of the time-consuming task are blocked, how long is a "time-consuming operation"? -Anything that interferes with the user's interaction with the program is counted. One second is longer, and all over two seconds is definitely too long.
In this section, our goal is to ensure functionality while preventing the user interface from being blocked and annoying users. To do this, take a look at the solution. There are two ways to achieve the ultimate goal of performing calculations:
- In the main thread calculation (single threaded mode)
- Separate threads (multi-threaded mode)
The latter is widely known, but it is sometimes abused. Because sometimes a thread can handle it completely. Contrary to popular belief, threads often slow down your application rather than accelerate. So, unless you're sure that the program can benefit from multiple threads (both in terms of efficiency and simplicity), try to avoid creating new threads as you can.
Manual Event handling
The most basic solution is to explicitly require QT to handle wait events at some point in the calculation. To do this, you must periodically call Qcoreapplication::p rocessevents ().
The following example shows how to do this:
for (int i = 3 ; I <= sqrt (x ) && IsPrime; i + = 2 ) {label->settext (tr (" Checking %1 ... "). Arg (i)); if (x % i = 0 ) IsPrime = false; Qcoreapplication::p rocessevents (); if (!pushbutton->ischecked ()) {Label->settext (tr ( "aborted" )); return ; }}
There are obvious drawbacks to this approach. For example, suppose you want to call two loops in parallel, one of which will block the other until the first one is done (so you can't allocate computing power to different tasks). This also causes the application's events to delay the response. In addition, the code is difficult to read and analyze, so this approach is only suitable for short operations that are processed in a single thread, such as: splash screen and short-term monitoring operations.
Use a worker thread
Another solution to avoid blocking the main event loop: To handle time-consuming operations in a separate thread. This is useful if the task is executed by a third-party library in a blocking manner. In this case, you may not be able to interrupt it to let the GUI handle the wait event.
One way to perform operations on separate threads is to use Qthread. You can subclass the Qthread and implement the Run () function, or call Qthread::exec () to start the thread's event loop, or both, of course. The signal slot can then be used to communicate with the main thread. Remember: You must make sure that you use the Qt::queuedconnection type connection, or the thread may lose stability and cause the program to crash.
There are many examples of threads used in QT reference documents and online materials, so this is not the case, but it is focused on other interesting aspects.
Wait for local event loop
Next to the solution, I want to describe the processing wait until the asynchronous task finishes. Here, I'll show you how to block the flow until the network operation is complete without blocking event processing, basically:
task.start();while (!task.isFinished()) QCoreApplication::processEvents();
This is called busy waiting-constantly checking the status until the condition is not met. In most cases, this is a bad idea-they tend to eat all the CPUs and have all the drawbacks of manual event handling.
Fortunately, QT has a class that can help us complete the task-Qeventloop: With the application and modal dialogs using exec (). Each instance of this class is connected to the main event scheduling mechanism, and when the Exec () function is called, it starts processing the event until it exits with quit ().
We can use this mechanism to convert asynchronous operations into synchronous operations, using signals and slots-to open a local event loop and let it exit when a specific signal is received for a particular object:
qnetworkaccessmanager manager; Qeventloop loop ; Qtimer timer;timer.setsingleshot (true ), connect (&timer, SIGNAL (timeout ()), &loop , SLOT (Quit ())), connect (&manager, SIGNAL (finished) ), &loop , SLOT (Quit ())); Qnetworkreply *reply = Manager.get (Qnetworkrequest (Qurl ()); Timer.start (5000 ); //5s timeout loop . EXEC (); if (Timer.isactive ()) {//Download complete timer.stop ();} else {//timeout }
We used Qnetworkaccessmanager to get the remote URL because it was asynchronous and we created a local event loop waiting for the finished () signal. In addition, a timer with a timeout of 5s is created, and the event loop is terminated in case of an error. Call Get () and start () to send the request and start the timer to enter the event loop. EXEC () returns after the download is complete or the 5s time-out expires (depending on who completes it first). By detecting whether the timer is active, we can determine who completed it first, and then we could process the result or tell the user that the download failed.
Here, we should say two things:
A similar approach is part of the Qxtsignalwaiter class in the LIBQXT project (http://www.libqxt.org).
For some operations, QT provides a "Wait" method (for example: Qiodevice::waitforbyteswritten ()), which is more or less the same as the above code, but does not run the event loop. However, the "wait" solution will block the GUI because they do not run their own event loops.
To solve the problem gradually
If you can divide the problem into sub-problems, then there is a good solution that does not block the GUI. In short, you can perform tasks within shorter steps without blocking time-consuming events. Saves its state and returns to the event loop when it has been found to have taken some time on the specified task. After completing the event, there is a way to notify QT to continue with your task.
Fortunately, there are two ways of doing it.
Mode one: Use a single trigger timer (time interval is 0). This particular value causes QT to emit a timeout () signal, and the event loop of the timer becomes idle. If you connect this signal to a slot function, you get the mechanism to call the function when the application is not busy doing other things (similar to how screen saver works). Here we look at an example of a background lookup prime:
Class Findprimes: Publicqobject{Q_object Public:Findprimes(Qobject *parent =0): Qobject () {} PublicSlotsvoid Start(Qlonglong _max);PrivateSlotsvoid Calculate(); Signals:voidPrime (Qlonglong);voidFinished ();Private: Qlonglong cand, Max, Curr;DoublesqrtvoidNext () {cand+=2; Curr =3; sqrt =:: sqrt (cand);}};voidFindprimes::start (Qlonglong _max) {Emit prime (1); Emit Prime (2); Emit Prime (3); max = _max; Cand =3; Curr =3; Next (); Qtimer::singleshot (0, This, SLOT (Calculate ())); }voidFindprimes::calculate () {Qtime T; T.start (); while(T.elapsed () < Max) {if(Cand > Max) {emit finished ();//End return; }if(Curr > sqrt) {Emit prime (cand);//PrimeNext (); }Else if(cand% Curr = =0) Next ();//Not prime ElseCurr + =2;//Check next divisor} qtimer::singleshot (0, This, SLOT (Calculate ()));}
The Findprimes class uses two attributes-keeping its current calculated state (cand, Curr variable) so that it can continue to compute the break, and can monitor (by using qtime::elapsed ()) How long the current task step has been executed. If the time exceeds the predetermined amount, it is returned to the event loop. But before doing so, it starts a single timer that will call the method again (you might call this a "delayed recurrence").
Mode two: This method can call any slot function in any object. One thing to say is that this works in our case, we need to make sure that the Qt::queuedconnection connection type is used so that the slot function is called asynchronously (by default, the slot function is called synchronously in a single thread). Therefore, we may replace the timer call with the following:
QMetaObject::invokeMethod"calculate", Qt::QueuedConnection);
The good thing about using this method than the timer is that it can pass parameters to the slot function (for example, pass the current state of the calculation to it), except that the two methods are equivalent.
Parallel programming
Finally, there is the case-a similar operation needs to be performed for a set of data. For example, to create thumbnails for a picture of a catalog, take a look at the simplest implementations:
QList<QImage>= loadImages(directory);QList<QImage>&image, images) { << image.scaled(QSize(300,300), Qt::KeepAspectRatio, Qt::SmoothTransformation); QCoreApplication::sendPostedEvents();}
The downside to this approach is that creating a single thumbnail can take a long time, when the GUI will block, and a better way to do this is in a separate thread:
QList<QImage>=*thread=new ThumbThread;connect(thread, SIGNAL(finished(QList<QImage>)), this, SLOT(showThumbnails(QList<QImage>)));thread->start(images);
This solution is very good, but does not take into account the direction of computer system development. Increasingly fast processing units are equipped with multiple slow units (multicore or multiprocessor systems) that together provide more compute cycles with low power dissipation and cooling. Unfortunately, the above algorithm uses only one thread and therefore executes on a single processing unit, resulting in slower execution on multicore systems than single cores (because one core in a multicore system is slower than one in a single core system).
To overcome this shortcoming, we have to go into the world of parallel programming-dividing the work into as many threads as possible to handle the available units, which are provided by Qthreadpool and QT concurrent.
The first possible approach is to use the so-called Runnables-simple class, where an instance can be executed by a thread. QT uses the Qrunnable class to achieve runnables. You can implement your own runnable based on the interface provided by Qrunnable, and execute it using another entity provided by QT. Refers to a thread pool-an object that can generate a large number of threads to perform arbitrary work. If the number of jobs exceeds the number of threads available, the job will be queued and the job will execute when the thread is available.
Back to the example, implement runnable and use the thread pool to create a thumbnail of an image.
class ThumbRunnable : public QRunnable {public: ThumbRunnable(...) ... {} void run(){ m_result = m_image.scaled(...); } return m_result; }};QList<ThumbRunnable *> runnables;foreach(const QImage &image, images){ ...); r->setAutoDelete(false); QThreadPool::globalInstance()->start(r); runnables << r;}
Basically, what needs to be done is to implement the run () function through the Qrunnable class, which is the same as the subclass Qthread, except that the job is not dependent on a thread it creates, so it can be called from any existing thread. After creating an instance of thumbrunnable, we want to make sure that it is not deleted by the thread pool after the job execution completes. This is done because we want to get the result of the object. Finally, we ask the thread pool to queue jobs to take advantage of each application's global thread pool variables and add runnable to the list for future reference.
Then, we need to check each runnable regularly to see if its results are available, more boring and troublesome. Fortunately, there is a better solution when you need to get results. Qt Concurrent introduces a series of examples that can perform SIMD (single instruction multi-data) operations. Let's take a look at one of these, the simplest of which is to process each element in the container and save the result in another container.
typedef QFutureWatcher<QImage> ImageWatcher;QImage makeThumb(const QString &img){ return QImage(img).scaled(QSize(300,300...);}QStringList images = imageEntries(directory);ImageWatcher *watcher = new ImageWatcher(this);connect(watcher, SIGNAL(progressValueChanged(int)), progressBar, SLOT(setValue(int)));QFuture<QImage> result = QtConcurrent::mapped(images, makeThumb);watcher->setFuture(result);
It's simple, isn't it? Only a few lines of code are required, and the observer notifies us of the state of the SIMD program held by the Qfuture object. It will even let us cancel, pause and resume the program. Here, we used a call to the simplest possible variable-using a standalone function. In a real situation, something more complicated than a simple function is used. Qt concurrent allows you to use functions, class functions, and Function objects, and third-party solutions allow you to further expand the possibilities by using binding function parameters.
Summarize
Above, a holistic solution based on QT time-consuming operation types and complexity issues has been demonstrated. These are just basics and can be relied upon-for example, using local event loops to create your own "schema" objects, using parallel programming to quickly process data, or using a thread pool to handle generic job runs. For simple cases, there is a way to manually request an application to handle a pending event, and dividing a complex task into smaller subtasks may be the right direction.
Never let your GUI block again!
More references
Keeping the GUI responsive
Qt to keep the GUI responsive