![]() |
Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
|
This feature was created to handle the rendering case in Qt PDF:
Multi-threading tends to lead to race conditions, in general. But the Image.asynchronous API blithely sets the expectation that loading of any image format can be threaded, to avoid UI stuttering. For most image formats, reading consists of nothing more than reading from a file or network connection, and straightforward format interpretation. But since PDF is treated as an image format (in spite of its complexity), we need to at least support the use of one worker thread to separate PDFium's work from the UI thread. But the work may need to be interrupted when a page-rendering job is no longer relevant, due to delegates scrolling out of view, change in zoom level, closing the document etc.
Here are some details about how we handle opening a different document while pages from the previous document are still being rendered:
① QQuickPdfPageImage::load() calls ② carrierFile() to get a ③ QPdfFile instance (a subclass of QIODevice), and calls ④ QQuickPixmap::loadImageFromDevice() which ⑤ saves the device to QQuickPixmapData::specialDevice, a QPointer. Then it calls ⑥ QQuickPixmapReader::startJob() which ⑦ posts a ProcessJobs event. The worker thread ⑪ handles it in QQuickPixmapReader::processJob() and calls ⑫ readImage(), passing a simple QIODevice pointer extracted from the specialDevice QPointer. readImage() relies on that pointer being valid until the work is done. This work includes calling ⑬ QPdfIOHandler::load(), which gets the QPdfDocument pointer from QPdfFile::document() and then calls ⑮ QPdfFile::render().
The rendering job may be cancelled at any time: the user may continue scrolling through delegates in a TableView or ListView, or may load a different document. In the latter case, ⑨ QQuickPdfDocument::setSource() calls ⑩ deleteLater() on the same QPdfFile instance that is being stored in QQuickPixmapData::specialDevice. If the rendering job has not yet started, QQuickPixmapReader::processJob() detects that either deleteLater() has been called or the QPointer is already null, and cancels the job. But what if it happens while the rendering job is already running? In practice, we do not see a crash in this case. But for the sake of prevention, if we change the thread affinity of specialDevice to the rendering job's thread, we can ensure that it cannot happen until after readImage() is done (it doesn't return to the event loop until it's done). This requires cooperation because QObject::moveToThread()
can only "push" an object from the current thread to another thread, it cannot "pull" an object from any arbitrary thread to the current thread.
The expectation is that QPdfFile's constructor calls moveToThread(nullptr). If that has been done, QQuickPixmapReader::processJob() can see that specialDevice->thread() == nullptr, and can change the affinity to its own thread. Otherwise, it crosses its fingers and leaves the thread affinity as-is.