Reading from a QFile in different thread

2019-06-06 16:17发布

What would be the optimal way of reading from a QFile that resides in a different thread than the one I want to read from?

Consider:

class AFile : public QObject
{
    Q_OBJECT
public:
    AFile(const QString &name, QObject *parent = Q_NULLPTR) : QObject(parent), m_File(name) { m_File.open(QIODevice::ReadWrite); }

public slots:
    void write(const QByteArray &data, qint64 pos) { m_File.seek(pos); m_File.write(data); }

private:
    mutable QFile m_File;
};

class AData : public QObject
{
    Q_OBJECT
public:
    using QObject::QObject;

    void save(const QByteArray &data) { emit saveData(data, 0); }

signals:
    void saveData(const QByteArray &data, qint64 pos) const;
};

AFile file("myfile");
AData data;
QThread *thread = new QThread;

connect(&data, &AData::saveData, &file, &AFile::write);

file.moveToThread(&thread);
thread.start();
data.save("Some data");

//how to concurrently read though?

AFile is a wrapper around QFile that handles all writes to the file. It is moved to a different thread in order to not slow the main thread with expensive disk write operations. Multi-thraeded read situations are handled by locks or mutex but that would defeat the purpose of having the file in the different thread in the first place since the main one (wanting to read from it) will have to wait for the write to finish so in such a case I may leave it in the main thread to begin with.

The option I do not quite like are signals and slots because it seems to me a bit heavy weight. I would basically send a request for the data and wait for it to be read (either returning the data via signal or sending variable to be filled with the data and waiting for a signal it has been finished).

After some consideration this seems to be the best approach but I am not really sure it is a good design.

1条回答
一夜七次
2楼-- · 2019-06-06 17:13

Concurrent reading is done by splitting the request and indication parts: you first request the data, the reader reads it, then you react to the indication that a data was read.

Since the file object is accessed from a worker thread, I've encapsulated it inside the AData class. You can move the class to a worker thread, the signals you call from outside are thread-safe.

It should also be possible to make an asynchronous wrapper around a QFile, but doing it properly would require using Qt's implementation details (core_private module).

// https://github.com/KubaO/stackoverflown/tree/master/questions/async-file-io-39226814
#include <QtWidgets>

class AData : public QObject
{
    Q_OBJECT
    QFile m_file;
public:
    explicit AData(QObject * parent = nullptr) : QObject{parent} {
        connect(this, &AData::save, this, [=](const QByteArray & data, qint64 pos){
            m_file.seek(pos);
            m_file.write(data);
        });
        connect(this, &AData::load, this, [=](qint64 pos, qint64 len){
           m_file.seek(pos);
           if (len == -1) len = m_file.size();
           auto data = m_file.read(len);
           emit loaded(data, pos);
        });
    }
    bool open(const QString & name) {
        m_file.setFileName(name);
        return m_file.open(QIODevice::ReadWrite);
    }
    Q_SIGNAL void save(const QByteArray &data, qint64 pos = 0) const;
    Q_SIGNAL void load(qint64 pos, qint64 len) const;
    Q_SIGNAL void loaded(const QByteArray &data, qint64 pos) const;
};

A test UI demonstrates how to use it:

int main(int argc, char ** argv) {
    QApplication app{argc, argv};
    struct Thread : QThread {
        ~Thread() { quit(); wait(); }
    } ioThread;
    AData data;
    data.open("myfile");
    data.moveToThread(&ioThread);
    ioThread.start();

    QWidget ui;
    QGridLayout layout{&ui};
    QTextEdit text;
    QPushButton load{"Load"};
    QPushButton save{"Save"};
    QPushButton clear{"Clear"};
    layout.addWidget(&text, 0, 0, 1, 2);
    layout.addWidget(&load, 1, 0);
    layout.addWidget(&save, 1, 1);
    layout.addWidget(&clear, 2, 0, 1, 2);
    ui.show();

    using Q = QObject;
    Q::connect(&load, &QPushButton::clicked, &data, [&]{
        data.load(0, -1);
    });
    Q::connect(&data, &AData::loaded, &app, [&](const QByteArray & data, qint64){
       text.setPlainText(QString::fromUtf8(data));
    });
    Q::connect(&save, &QPushButton::clicked, &data, [&]{
        data.save(text.document()->toPlainText().toUtf8());
    });
    Q::connect(&clear, &QPushButton::clicked, &text, &QTextEdit::clear);

    return app.exec();
}
#include "main.moc"
查看更多
登录 后发表回答