Waiting for a signal within a Qquickimageprovider

Asked

Viewed 205 times

15

I’m creating an application using QML and the Qt 5.2. In her a ListView displays multiple items, each with an image and associated text. The image is built based on data uploaded from a server by HTTP. Simply put, I have the following code:

MyProvider::MyProvider() :
    QQuickImageProvider(QQmlImageProviderBase::Image,
                        QQmlImageProviderBase::ForceAsynchronousImageLoading)
{ }

QImage MyProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize)
{
    // Obter dados JSON que me explicam como montar a imagem
    QNetworkAccessManager manager;
    QNetworkRequest request(QUrl("http://myserver.com/api/imagedata/" + id));
    request.setRawHeader("Accept", "application/json");
    QNetworkReply* reply = manager.get(request);

    // Aguardar pela resposta. Aqui está o problema
    QEventLoop loop;
    QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
    loop.exec();

    // Ler a resposta e montar uma imagem com ela
    QImage img = produceImageFromJsonData(reply->readAll());
    delete reply;

    // Ajustar para o tamanho requisitado
    if (requestedSize.isValid())
        img = img.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
    *size = img.size();
    return img;
}

The MyProvider is then recorded and used in QML as source of each Image from the list. The problem with this code is that there is a race condition in it. The requestImage is executed in a thread different from the rest of the application. At the moment I create a QEventLoop and run it, I’m allowing my thread to receive and process any event occurring. Since there can be more than one thread running this loop, two events can be sent to the same object at the same time by different threads. I get a crash difficult to reproduce (it happened for the first time today, after almost a month of development).

The problem can also be reproduced with this smaller code, showing that the simple existence of a QEventLoop triggers the crash:

MyProvider::MyProvider() :
    QQuickImageProvider(QQmlImageProviderBase::Image,
                        QQmlImageProviderBase::ForceAsynchronousImageLoading)
{ }

QImage MyProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize)
{
    QEventLoop loop;
    loop.exec();
    return QImage();
}

I found a bugreport what date of the Qt 4.7.1 where the following is said:

The problem is that the QEventLoop that is created in the imageprovider causes Events to be Delivered to the image Reader which shares the thread. It receives These Events while still Processing a Previous Event. [...] It is not Valid to run an Event loop in the image Provider.

In short, I cannot use the QEventLoop in my role. So how can I wait for the QNetworkReply and only return from the function when the answer arrives?

  • Qnetworkreply has an 'finished' sign (http://qt-project.org/doc/qt-4.8/qnetworkreply.html#finished) which is emitted when the response has finished processing. You tried to use it directly?

  • That’s the one I used QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));. The problem is that waiting for any sign implies receiving events.

  • Yes, but what I meant was, did you try to connect the signal to a method in your own class and put the 'produceImageFromJsonData' call there (something like connect(reply, SIGNAL(finished()), this /* slot in this, and not loop */, SLOT(finished()));)? In other words, you really need the loop?

  • @Luizvieira, does not solve the problem, because Qquickimageprovider expects me to return to Qimage for that function. The moment I return the thread may be destroyed.

  • @Bacco, this is exactly the same as the loop Event. But note that this is not in the same thread as the UI, so I don’t have to worry about freezing. The problem is that I cannot process events and Qnetworkreply needs me to process them to work (merely using a loop with a Sleep inside does not work).

  • @You’re right, I’m sorry. Well, I have another test suggestion for you: try using Qt::Queuedconnection as the connection type. The default is 'auto', and since Qnetworkreply and Qeventloop are in the same thread maybe the bug report problem you cited (It receives These Events while still Processing a Previous Event) is occurring because of direct sending. Who knows if by forcing the finish signal queuing the problem no longer occurs (although it is difficult to reproduce, as you mentioned).

  • @Luizvieira Nothing to apologize, is helping! To what I understood mine QEventLoop is receiving and processing signals from other objects of other threads (some internal QML that is in the UI thread). As there is more than one thread of mine, this QML object receives two signals at the same time from separate threads. Also the Bugger showed that the crash happened with some other signal, even before the signal QNetworkReply be eviado. I can reproduce the problem by just QEventLoop().exec(); inside that function, deleting everything else.

  • @Guilhermebernal What a hair problem, huh? hehe The error still occurs (or changes) if you use Qeventloop(). exec(Qeventloop::Exclude serinputevents); ?

  • @Luizvieira Yes, I tried that too. Unfortunately I have the same crash.

  • @Guilhermebernal I imagined that you had already tested, but it wouldn’t hurt to ask. : ) Well, I will research more as soon as I have more time. If you have any more suggestions I will post. Good luck there.

Show 5 more comments

1 answer

6


I don’t know how to wait for the signal there. It is also not possible to make the thread sleep in a loop by checking if the data arrived manually because the QNetworkReply depends on the Event loop to be notified of receipt of the reply.


So I went to a different implementation of the same idea. Instead of creating an image provider and letting the engine take care of everything in place, I chose to do it more manually.

Previous interface:

Image {
  id: avatar
  source: "image://myimageprovider/" + avatarImageId
}

New interface:

Image {
  id: avatar
}

MyImageLoader {
  sourceId: avatarImageId
  target: avatar
}

My goal has become to implement the component MyImageLoader so that I did the desired operation. Follow my implementation:

myimageloader.hpp:

class MyImageLoader : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString sourceId READ sourceId WRITE setSourceId)
    Q_PROPERTY(QObject* target READ target WRITE setTarget)

public:
    MyImageLoader(QObject* parent = 0) : QObject(parent) {}

    QString sourceId() const { return _sourceId; }
    void setSourceId(const QString& sourceId) { _sourceId = sourceId; beginDownload(); }

    QObject* target() const { return _target; }
    void setTarget(QObject* target) {_target = target; setTargetSource(); }

private:
    void beginDownload();
    void endDownload();
    void setTargetSource();
    QString cacheFile();

    QNetworkAccessManager _manager;
    QNetworkReply* _reply = nullptr;
    QString _sourceId;
    QObject* _target = nullptr;
};

myimageloader.cpp:

void MyImageLoader::beginDownload() {
    if (QFile::exists(cacheFile()))
        return setTargetSource();

    delete _reply;
    QNetworkRequest request(QUrl("http://myserver.com/api/imagedata/" + _sourceId));
    request.setRawHeader("Accept", "application/json");
    _reply = _manager.get(request);
    connect(_reply, &QNetworkReply::finished, this, &MyImageLoader::endDownload);
}

void MyImageLoader::endDownload() {
    QImage img = produceImageFromJsonData(_reply->readAll());
    delete _reply;
    img.save(cacheFile());
    setTargetSource();
}

void MyImageLoader::setTargetSource() {
    if (!_target) return;

    QString file = cacheFile();
    _target->setProperty("source", QFile::exists(file) ? "file:///" + file : "");
}

QString MyImageLoader::cacheFile() {
    return QDir::temp().filePath(_sourceId + ".png");
}

The big difference is that I’m not using threads. It all takes place in a single thread (the same one that renders the scene). There is no locking of the interface while loading on account of the signals. All these functions return immediately and do not lock.

The downside is that I’m forced to save the image in a file. It’s not a problem for me, since I planned to cache the data anyway.

That doesn’t answer the question itself, but it solves my problem.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.