2016-08-30 2 views
1

Каким будет оптимальный способ чтения из QFile, который находится в другом потоке, чем тот, который я хочу прочитать?Чтение из QFile в другой теме

Рассмотрим:

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 является оберткой QFile, который обрабатывает все записи в файл. Он перемещается в другой поток, чтобы не замедлять основной поток с дорогостоящими операциями записи на диск. Многостраничные ситуации чтения обрабатываются блокировками или мьютексами, но это может привести к тому, что файл сначала будет в другом потоке, поскольку основной (желающий прочитать из него) должен будет дождаться завершения записи в таком случае я могу оставить его в основном потоке для начала.

Вариант, который мне не совсем нравится, - это сигналы и слоты, потому что мне кажется немного тяжелым. Я бы в основном отправил запрос на данные и дождался, пока он будет считаться (либо возвращающий данные через сигнал, либо отправляющую переменную, которая будет заполнена данными и ждет сигнала, который был закончен).

После некоторого рассмотрения это, кажется, лучший подход, но я не уверен, что это хороший дизайн.

+0

ли запись в файл действительно замедляет ваше приложение? Я бы ожидал, что ОС будет кэшировать их, если вы не заставили синхронизированный режим. Также правильно ли я понимаю, что вы хотите читать и записывать файл независимо без синхронизации? Я предполагаю, что вы читаете и пишете в разных его частях. Затем вы можете просто открыть файл дважды, один раз для чтения и один раз для записи. – michalsrb

+1

@michalsrb Все синхронные операции ввода-вывода, выполненные из потока графического интерфейса пользователя, являются основной причиной проблем с удобством использования и зависанием GUI - это было так с первого дня API-интерфейса UI, основанного на событиях. Именно поэтому современные API, такие как Universal Windows Platform, имеют в основном асинхронные API: если пользовательский интерфейс блокирует ожидание ввода-вывода, вы даете вашему пользователю плохой опыт. –

ответ

3

Параллельное считывание выполняется путем разделения частей запроса и индикации: вы сначала запрашиваете данные, читатель читает их, затем вы реагируете на указание, что данные были прочитаны.

Поскольку доступ к файловому объекту осуществляется из рабочего потока, я инкапсулировал его внутри класса AData. Вы можете переместить класс в рабочий поток, сигналы, которые вы вызываете извне, являются потокобезопасными.

Также должно быть возможно создать асинхронную оболочку вокруг QFile, но для ее правильного использования потребуется использовать детали реализации Qt (модуль core_private).

// 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; 
}; 

Тест UI демонстрирует, как использовать его:

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" 
+0

Это довольно мило и лаконично! У меня есть вопрос, однако, не использует ли это использование QtConcurrent состояние гонки? Предположим, что запись выполняется и отправляется QtConcurrent в некоторый поток. Перед тем, как закончить, читается и отправляется в том же месте. Что случится? Защищает ли QtConcurrent от этой ситуации? m_File - это общий ресурс здесь, как я его понимаю. – Resurrection

+0

Это общий ресурс, и я не уверен, является ли 'QFile' устойчивым к такому доступу или нет. Можно гарантировать, что за один раз будет выполняться только один доступ, я отредактирую его позже. –

+0

Я уже пробовал, и он не устойчив к нему.Чтобы решить эту проблему, я сначала попытался заблокировать, но в итоге вернулся к тому, что не использовал QtConcurrent и перемещал оболочку в один другой поток, потому что таким образом он не замедляет поток основного (GUI) и не имеет «блокировки» накладных расходов, поскольку гарантируется, что все обращения никогда не будут параллельно, поскольку все они выполняются в одном потоке. Я с нетерпением жду вашего решения (вероятно, лучше, чем у меня :-)). – Resurrection

Смежные вопросы