Async I/O
Qt
File Handling
Programming
C++

How to do async file io in qt?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Qt makes asynchronous network I/O easy, but local file I/O is different. QFile itself is blocking, so if you want non-blocking behavior for disk reads or writes, the usual Qt solution is to move the work off the GUI thread rather than expecting QFile to become event-driven on its own.

What "Async" Means For Local Files In Qt

For sockets and network replies, Qt can notify you when data is ready through the event loop. Disk files do not work that way in Qt's API. A call such as file.readAll() blocks the current thread until the OS completes the operation.

So in a Qt application, asynchronous file I/O usually means:

  • Run file work in a worker thread.
  • Emit signals when data is ready.
  • Keep the main thread free for UI updates.

This is why QNetworkAccessManager is not the right answer for local files. It is asynchronous, but it solves a different problem.

A Worker Object With QThread

The most explicit pattern is a worker QObject moved to a QThread:

cpp
1#include <QCoreApplication>
2#include <QFile>
3#include <QObject>
4#include <QThread>
5#include <QDebug>
6
7class FileReader : public QObject {
8    Q_OBJECT
9public slots:
10    void readFile(const QString &path) {
11        QFile file(path);
12        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
13            emit error(file.errorString());
14            emit finished();
15            return;
16        }
17
18        const QByteArray data = file.readAll();
19        emit dataReady(data);
20        emit finished();
21    }
22
23signals:
24    void dataReady(const QByteArray &data);
25    void error(const QString &message);
26    void finished();
27};

And the setup:

cpp
1QThread *thread = new QThread;
2FileReader *reader = new FileReader;
3
4reader->moveToThread(thread);
5
6QObject::connect(thread, &QThread::started, [reader]() {
7    reader->readFile("example.txt");
8});
9
10QObject::connect(reader, &FileReader::dataReady, [](const QByteArray &data) {
11    qDebug() << "Read bytes:" << data.size();
12});
13
14QObject::connect(reader, &FileReader::error, [](const QString &message) {
15    qWarning() << "Read failed:" << message;
16});
17
18QObject::connect(reader, &FileReader::finished, thread, &QThread::quit);
19QObject::connect(reader, &FileReader::finished, reader, &QObject::deleteLater);
20QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
21
22thread->start();

This keeps the file operation off the main thread and reports results through signals.

A Simpler Option With QtConcurrent

If you only need to run a task in the background and do not need a custom worker object, QtConcurrent::run is simpler:

cpp
1#include <QFile>
2#include <QFutureWatcher>
3#include <QtConcurrent>
4#include <QDebug>
5
6auto future = QtConcurrent::run([]() -> QByteArray {
7    QFile file("example.txt");
8    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
9        return {};
10    }
11    return file.readAll();
12});
13
14auto *watcher = new QFutureWatcher<QByteArray>();
15
16QObject::connect(watcher, &QFutureWatcher<QByteArray>::finished, [watcher]() {
17    qDebug() << "Read bytes:" << watcher->result().size();
18    watcher->deleteLater();
19});
20
21watcher->setFuture(future);

This is good for one-off background reads, but it gives you less fine-grained control than a dedicated worker object.

Writing Files Asynchronously

The same pattern works for writes. Put the blocking QFile::write call in a worker thread and emit completion:

cpp
1void writeFile(const QString &path, const QByteArray &data) {
2    QFile file(path);
3    if (!file.open(QIODevice::WriteOnly)) {
4        emit error(file.errorString());
5        emit finished();
6        return;
7    }
8
9    if (file.write(data) == -1) {
10        emit error(file.errorString());
11    }
12
13    file.flush();
14    emit finished();
15}

If the files are large, consider chunked reads or writes instead of readAll, so memory usage stays predictable.

Choosing The Right Approach

Use QThread with a worker object when:

  • You need progress reporting.
  • You need cancellation or multiple operations.
  • You want strong lifecycle control.

Use QtConcurrent when:

  • The task is simple and self-contained.
  • You only need the final result.
  • You want less boilerplate.

Common Pitfalls

The biggest mistake is assuming QFile is asynchronous because Qt uses signals and slots heavily elsewhere. QFile operations are synchronous and will freeze the UI if executed on the GUI thread.

Another common issue is reading entire large files with readAll. That may be fine for configuration files, but it is risky for logs, media, or large datasets. Stream large files in chunks.

Developers also sometimes access GUI widgets directly from the worker thread. Do not do that. Emit signals back to the main thread and update the UI there.

Finally, thread lifetime matters. If the worker or thread is destroyed too early, callbacks may never fire or the app may crash during shutdown.

Summary

  • Local file I/O with QFile is blocking in Qt.
  • To make file operations asynchronous, move them off the GUI thread.
  • 'QThread plus a worker object is the standard flexible pattern.'
  • 'QtConcurrent::run is a good lightweight option for simple tasks.'
  • Keep UI updates on the main thread and avoid readAll for huge files.

Course illustration
Course illustration

All Rights Reserved.