Question

I am running Qt 5.1 and QtQuick 2.0 on a Mac with OS-X 10.8.4.

My Qt-QML GUI becomes unresponsive because I am blocking the event loop with file I/O operations. The usual approach to solving it is to use multiple threads as discussed HERE by Will Bickford.

To do so, I am attempting to use:

QtConcurrent::blockingMapped() 

which can be simpler than using an explicit QFuture object. I have been reading the Qt docs and cool examples and got the following code working (modeled after this example):

// NOTE: this all seems to work:
#include <QList>
#include <iostream>
#include "dataobject.h"
#include <QtConcurrent/QtConcurrentMap>

DataObject load(const QString &file) {
    std::cout << "File I/O in thread = " << QThread::currentThread() << std::endl;
    return DataObject anObject(file);
}

int main(int argc, char *argv[])
{
    ...

    // Create a list of filenames:
    int count = 5;
    QList<QString> allFiles;
    for (int i = 0; i < count; i++) {
        allFiles.append(QString("aFileName"));
    }
    std::cout << "# of files = " << allFiles.size() << std::endl;

    QList<DataObject> allTheDataObjects = QtConcurrent::blockingMapped(allFiles,load);
    std::cout << "# of objects = " << allTheDataObjects.size() << std::endl;

    ...
}

and here are the header and implementation files for DataObject:

#include <QString>
class DataObject
{
public:
    DataObject();
    DataObject(QString filename);
    QString theFileName;
};

#include "dataobject.h"

DataObject::DataObject() {
    theFileName = QString("no file");
}
DataObject::DataObject(QString filename) {
    theFileName = filename;
    //...
    // Do file I/O stuff (slow) ...
}

This is not very realistic but serves as a simple sketch to illustrate the problem I encountered below.

The problem occurs when I try to encapsulate QtConcurrent::blockingMapped() within an additional "datamodel.h" class:

#include "dataobject.h"
class DataModel
{
public:
    DataModel();
    void runConcurrent();
    DataObject load(const QString& fileList);
};

#include "datamodel.h"
#include <QtConcurrent/QtConcurrentMap>
#include <iostream>

DataModel::DataModel() {}
DataObject DataModel::load(const QString &file) {
    std::cout << "File I/O in thread = " << QThread::currentThread() << std::endl;
    return DataObject anObject(file);
}
void DataModel::runConcurrent() {
    // Create a list of filenames:
    int count = 5;
    QList<QString> allFiles;
    for (int i = 0; i < count; i++)
        allFiles.append(QString("dummyFileName"));
    QList<DataObject> allTheDataObjects = QtConcurrent::blockingMapped(allFiles, this->load);
    std::cout << "# of objects = " << allTheDataObjects.size() << std::endl;
}

And then main() becomes (note that I also moved the load() method into the DataModel class):

#include <QList>
#include <iostream>
#include "dataobject.h"
#include "datamodel.h"
#include <QtConcurrent/QtConcurrentMap>

int main(int argc, char *argv[])
{
    ...

    DataModel theModel;
    theModel.runConcurrent();

    ...
}

But now there is a compiler error:

datamodel.cpp: error: reference to non-static member function must be called:
QList<DataObject> allTheDataObjects = QtConcurrent::blockingMapped(allFiles, this->load);

I was not able to fix the compiler error by initializing a DataObject or DataModel instance (so that a non-static member function could be visible) and wasn't sure what else to try.

Next I suspected this could be due to a problem with "functor" bindings when setting up the QtConcurrent arguments (I don't have boost installed so am not using boost::bind) so I tried Mat's suggestion of using C++ 11 lambdas by replacing:

this->load

with:

[this](const QString& file){load(file);}

giving the code:

QList<DataObject> allTheDataObjects = QtConcurrent::blockingMapped(allFiles, 
                                       [this](const QString& file){load(file);});

Now I no longer get the non-static member error, but a new error occurs (pointing at the above line):

datamodel.cpp: error: expected expression:

I've really gotten down a rabbit hole on this one, its probably a simple mistake but I am having trouble sorting it out.

Can someone help?

Was it helpful?

Solution 2

Ok, got it to work.

In case this can benefit those who may not want to mess with bindings or write their own "functor" (the approach below is way simpler):

Start by using the static type in datamodel.h file for the "load" function:

static DataObject load(const QString& fileList);

This works because static functions of a C++ class can be called without the need for instantiating the object.

Then, in the datamodel.cpp file, it is this simple:

DataModel::load 

to give:

QList<DataObject> allTheDataObjects = 
                      QtConcurrent::blockingMapped(allFiles, DataModel::load);

Then the code runs as expected.

OTHER TIPS

Apparently you don't use C++11 which has lambda expressions. So use binds from STL or Boost or create function object.

QList<DataObject> allTheDataObjects = QtConcurrent::blockingMapped(allFiles, 
                                   std::bind1st(std::mem_fun(&DataModel::load), this));

Ok above solution cant work with references (know bug). Define functor (or use Boost::bind):

class LoadDataModel /*: public std::unary_function<QString, DataObject>*/ {
public:
    LoadDataModel(DataModel *p) : d(p) {}

    DataObject operator()(const QString &s) const {
        return d->load(s);
    }
private:
    DataModel *d;
}

QList<DataObject> allTheDataObjects = QtConcurrent::blockingMapped(allFiles, LoadDataModel(this));
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top