Qt-for-鸿蒙PC-多线程绘制开源鸿蒙开发实践

Qt for 鸿蒙PC 多线程绘制开源鸿蒙开发实践

📋 项目概述

项目地址:https://gitcode.com/szkygc/HarmonyOs_PC-PGC/tree/main/Multithreading

本文档基于一个完整的 Multithreading 项目,详细介绍了如何在 HarmonyOS 平台上使用 Qt 实现多线程绘制功能。项目实现了两个独立的工作线程同时绘制圆形和方形,展示了 Qt 多线程编程、QML 与 C++ 集成、Canvas 绘制等技术在 HarmonyOS 平台上的实际应用。

✨ 主要功能

  • 多线程绘制:两个独立线程同时绘制圆形和方形
  • Worker 模式:使用 QThread + Worker 模式,符合 Qt 最佳实践
  • C++/QML 集成:C++ 自定义类型注册到 QML,实现无缝集成
  • Canvas 实时绘制:使用 Canvas 2D API 实时绘制线程生成的点
  • 线程安全通信 :使用信号槽机制和 Qt::QueuedConnection 确保线程安全
  • 线程生命周期管理:完善的线程启动、停止和清理机制
  • 防止重复调用 :使用标志位防止 doWork 被重复调用
  • 自适应窗口调整:窗口尺寸变化时自动调整绘制参数
  • 优雅停止机制:支持线程中断和资源清理

🎯 技术亮点

多线程架构

  • 使用 QThread + Worker 模式,符合 Qt 最佳实践
  • 工作对象(Worker)在独立线程中运行
  • 主对象(Thread)在主线程中供 QML 使用
  • 使用 Qt::QueuedConnection 确保线程安全

线程管理

  • 防止重复调用 doWork 的机制
  • 工作完成后自动停止线程
  • 完善的线程清理和资源释放

HarmonyOS 适配

  • 使用 qtmain() 作为入口函数
  • OpenGL ES 配置
  • QML 与 C++ 类型注册

🛠️ 技术栈

  • 开发框架: Qt 5.15+ for HarmonyOS
  • 编程语言: C++ / QML / JavaScript
  • 多线程框架: QThread + Worker 模式
  • 图形渲染: Canvas 2D API
  • 界面框架: Qt Quick Controls 2
  • 构建工具: CMake
  • 目标平台: HarmonyOS (OpenHarmony) / PC

🏗️ 项目架构

目录结构

复制代码
Multithreading/
├── entry/src/main/
│   ├── cpp/
│   │   ├── main.cpp              # 应用入口(HarmonyOS适配)
│   │   ├── main.qml              # 主界面(Canvas绘制)
│   │   ├── CircleThread.h        # 圆形绘制线程头文件
│   │   ├── CircleThread.cpp      # 圆形绘制线程实现
│   │   ├── SquareThread.h        # 方形绘制线程头文件
│   │   ├── SquareThread.cpp      # 方形绘制线程实现
│   │   ├── CMakeLists.txt        # 构建配置
│   │   └── qml.qrc               # QML资源文件
│   ├── module.json5              # 模块配置
│   └── resources/                # 资源文件
└── image/
    └── 演示示例.gif              # 演示动图

组件层次结构

复制代码
ApplicationWindow (main.qml)
├── ColumnLayout
│   ├── Text (标题)
│   ├── Rectangle (画布容器)
│   │   └── Canvas (绘制组件)
│   │       ├── onPaint (绘制函数)
│   │       └── 绘制圆形点(蓝色)
│   │       └── 绘制方形点(红色)
│   ├── RowLayout (控制按钮)
│   │   ├── Button (开始绘制)
│   │   ├── Button (停止绘制)
│   │   └── Button (清空画布)
│   └── Text (状态显示)
├── CircleThread (C++ 线程对象)
│   ├── CircleWorker (工作对象)
│   │   └── doWork() (在工作线程中执行)
│   └── 信号: circlePoint, finished
└── SquareThread (C++ 线程对象)
    ├── SquareWorker (工作对象)
    │   └── doWork() (在工作线程中执行)
    └── 信号: squarePoint, finished

多线程架构设计

复制代码
主线程 (Main Thread)
├── ApplicationWindow (QML)
├── CircleThread (QObject)
│   ├── 管理 QThread
│   ├── 管理 CircleWorker
│   └── 信号槽连接 (Qt::QueuedConnection)
└── SquareThread (QObject)
    ├── 管理 QThread
    ├── 管理 SquareWorker
    └── 信号槽连接 (Qt::QueuedConnection)

工作线程 1 (Worker Thread 1)
└── CircleWorker
    └── doWork() - 计算圆形点

工作线程 2 (Worker Thread 2)
└── SquareWorker
    └── doWork() - 计算方形点

📝 核心功能实现

1. HarmonyOS 入口函数:qtmain()

⚠️ 关键要点 :HarmonyOS 真机上必须使用 qtmain() 而不是 main()

cpp 复制代码
// ✅ 正确写法
extern "C" int qtmain(int argc, char **argv)
{
    // Qt 应用作为共享库加载,生命周期由 HarmonyOS 管理
    QGuiApplication app(argc, argv);
  
    // 注册 C++ 类型到 QML
    qmlRegisterType<CircleThread>("Multithreading", 1, 0, "CircleThread");
    qmlRegisterType<SquareThread>("Multithreading", 1, 0, "SquareThread");
  
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();  // ⚠️ 重要:必须调用 exec() 启动事件循环
}

// ❌ 错误写法(桌面应用方式)
int main(int argc, char *argv[])
{
    // 这种方式在 HarmonyOS 上会导致应用无法正常启动
}

原因说明

  • HarmonyOS 将 Qt 应用作为共享库(.so)加载
  • 应用生命周期由 HarmonyOS 的 Ability 管理
  • qtmain() 是 HarmonyOS Qt 插件的标准入口点

2. OpenGL ES 表面格式配置

⚠️ 关键要点 :必须在创建 QGuiApplication 之前 配置 QSurfaceFormat

cpp 复制代码
// Step 1: 配置 OpenGL ES 表面格式(必须在创建应用之前!)
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);

QSurfaceFormat format;

// 设置 Alpha 通道(透明度)
format.setAlphaBufferSize(8);      // 8 位 Alpha 通道

// 设置颜色通道(RGBA 32 位真彩色)
format.setRedBufferSize(8);        // 8 位红色通道
format.setGreenBufferSize(8);      // 8 位绿色通道
format.setBlueBufferSize(8);       // 8 位蓝色通道

// 设置深度和模板缓冲区
format.setDepthBufferSize(24);     // 24 位深度缓冲
format.setStencilBufferSize(8);    // 8 位模板缓冲

// 指定渲染类型为 OpenGL ES(HarmonyOS要求)
format.setRenderableType(QSurfaceFormat::OpenGLES);

// 指定 OpenGL ES 版本为 2.0(推荐,兼容性更好)
format.setVersion(2, 0);
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
format.setSamples(0);

// ⚠️ 关键:必须在创建 QGuiApplication 之前设置默认格式!
QSurfaceFormat::setDefaultFormat(format);

// Step 2: 创建 Qt 应用实例(必须在设置格式之后)
QGuiApplication app(argc, argv);

3. Qt 多线程架构:Worker 模式

核心思想:将工作对象(Worker)移动到工作线程,主对象(Thread)在主线程中供 QML 使用。

3.1 Worker 类设计
cpp 复制代码
// 工作对象:实际在工作线程中运行
class CircleWorker : public QObject
{
    Q_OBJECT

public:
    explicit CircleWorker(QObject *parent = nullptr);
    void requestStop() { m_shouldStop = true; }

public slots:
    void doWork(const QPoint &center, int radius);

signals:
    void pointGenerated(const QPoint &pt);
    void workFinished();

private:
    bool m_shouldStop;
};

关键点

  • CircleWorker 继承自 QObject,可以使用信号槽
  • doWork() 是槽函数,在工作线程中执行
  • 通过信号 pointGeneratedworkFinished 与主线程通信
  • m_shouldStop 标志用于优雅停止
3.2 Thread 类设计
cpp 复制代码
// 主对象:始终在主线程,供QML使用
class CircleThread : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QPoint center READ center WRITE setCenter NOTIFY centerChanged)
    Q_PROPERTY(int radius READ radius WRITE setRadius NOTIFY radiusChanged)
    Q_PROPERTY(bool running READ running NOTIFY runningChanged)

public:
    explicit CircleThread(QObject *parent = nullptr);
    ~CircleThread();

    QPoint center() const { return m_center; }
    void setCenter(const QPoint &center);
  
    int radius() const { return m_radius; }
    void setRadius(int radius);
  
    bool running() const { return m_running; }

public slots:
    void start();
    void stop();

signals:
    void circlePoint(const QPoint &pt);
    void centerChanged();
    void radiusChanged();
    void runningChanged();
    void finished();

private slots:
    void onWorkerFinished();
    void onPointGenerated(const QPoint &pt);
    void onThreadStarted();

private:
    QPoint m_center;
    int m_radius;
    QThread *m_thread;
    CircleWorker *m_worker;
    bool m_running;
    bool m_workStarted;  // 防止重复调用 doWork
};

关键点

  • CircleThread 继承自 QObject,使用 Q_PROPERTY 暴露属性给 QML
  • 管理 QThreadCircleWorker 的生命周期
  • 使用 Qt::QueuedConnection 连接信号槽,确保线程安全

4. 线程初始化和 Worker 移动

⚠️ 关键要点:Worker 对象必须移动到工作线程!

cpp 复制代码
CircleThread::CircleThread(QObject *parent)
    : QObject(parent)
    , m_center(0, 0)
    , m_radius(0)
    , m_thread(nullptr)
    , m_worker(nullptr)
    , m_running(false)
    , m_workStarted(false)
{
    // 创建工作线程和工作对象
    m_thread = new QThread(this);
    m_worker = new CircleWorker();
  
    // ⚠️ 关键:将工作对象移动到工作线程
    // 这一步非常重要!只有移动到线程后,Worker 的槽函数才会在工作线程中执行
    m_worker->moveToThread(m_thread);
  
    // 连接信号槽:工作对象 -> 主对象
    // ⚠️ 使用 Qt::QueuedConnection 确保线程安全
    connect(m_worker, &CircleWorker::pointGenerated, 
            this, &CircleThread::onPointGenerated, 
            Qt::QueuedConnection);
    connect(m_worker, &CircleWorker::workFinished, 
            this, &CircleThread::onWorkerFinished, 
            Qt::QueuedConnection);
  
    // 连接线程信号:线程启动后开始工作
    connect(m_thread, &QThread::started, 
            this, &CircleThread::onThreadStarted);
  
    // 线程结束时清理工作对象
    connect(m_thread, &QThread::finished, 
            m_worker, &QObject::deleteLater);
}

为什么使用 Qt::QueuedConnection

  • Qt::QueuedConnection 确保信号在接收对象的线程中执行
  • Worker 在工作线程中发送信号,主对象在主线程中接收
  • 这样可以安全地更新 UI(QML 必须在主线程中更新)

5. 线程启动和防止重复调用

问题QThread::started 信号和备用机制可能同时触发,导致 doWork 被调用两次。

解决方案 :使用 m_workStarted 标志防止重复调用。

cpp 复制代码
void CircleThread::start()
{
    // 检查是否已经在运行
    if (m_running || !m_thread) {
        return;
    }
  
    // 如果线程已经在运行,先停止并等待完成
    if (m_thread->isRunning()) {
        stop();
        if (!m_thread->wait(2000)) {
            qWarning() << "CircleThread: 线程未在2秒内停止";
            return;
        }
    }
  
    // 重新创建工作对象(如果已被删除)
    if (!m_worker) {
        m_worker = new CircleWorker();
        m_worker->moveToThread(m_thread);
        // 重新连接信号槽...
    }
  
    // 重置标志
    m_running = true;
    m_workStarted = false;  // ⚠️ 重置工作开始标志
    emit runningChanged();
  
    // 启动线程
    m_thread->start();
  
    // 备用机制:如果信号没有触发,延迟调用 onThreadStarted
    QTimer::singleShot(50, this, [this]() {
        // ⚠️ 检查 m_workStarted,防止重复调用
        if (m_running && !m_workStarted && m_worker && m_thread->isRunning()) {
            qDebug() << "CircleThread: 备用机制 - 直接调用 onThreadStarted";
            onThreadStarted();
        }
    });
}

void CircleThread::onThreadStarted()
{
    // ⚠️ 防止重复调用
    if (m_workStarted) {
        qDebug() << "CircleThread: onThreadStarted 已被调用过,跳过重复调用";
        return;
    }
  
    // 检查 worker 是否存在
    if (!m_worker) {
        qWarning() << "CircleThread: m_worker 为 nullptr,无法调用 doWork";
        return;
    }
  
    // ⚠️ 标记工作已开始,防止重复调用
    m_workStarted = true;
  
    // 使用 QMetaObject::invokeMethod 在工作线程中调用 doWork
    bool success = QMetaObject::invokeMethod(m_worker, "doWork", 
                                             Qt::QueuedConnection,
                                             Q_ARG(QPoint, m_center),
                                             Q_ARG(int, m_radius));
  
    if (!success) {
        qWarning() << "CircleThread: invokeMethod 失败";
        m_workStarted = false;  // 如果调用失败,重置标志
    }
}

关键点

  • m_workStarted 标志确保 doWork 只被调用一次
  • 备用机制中也检查该标志,避免重复调用
  • 如果 invokeMethod 失败,重置标志以便重试

6. Worker 工作实现

圆形绘制:使用参数方程计算圆周上的点。

cpp 复制代码
void CircleWorker::doWork(const QPoint &center, int radius)
{
    qDebug() << "CircleWorker::doWork 开始 - center:" << center << "radius:" << radius;
    m_shouldStop = false;
  
    // 检查参数有效性
    if (radius <= 0) {
        qWarning() << "CircleWorker: radius 无效:" << radius;
        emit workFinished();
        return;
    }
  
    // 使用固定点数绘制圆形(360个点,每个点1度)
    const int totalPoints = 360;
    double radianIncrement = 2.0 * M_PI / totalPoints;
  
    int pointCount = 0;
  
    // 遍历所有点
    for (int i = 0; i < totalPoints && !m_shouldStop && !QThread::currentThread()->isInterruptionRequested(); ++i) {
        // 计算当前角度
        double circleAngle = i * radianIncrement;
      
        // 计算新点的位置(参数方程)
        int centerX = center.x();
        int centerY = center.y();
        int x = centerX + static_cast<int>(radius * cos(circleAngle));
        int y = centerY + static_cast<int>(radius * sin(circleAngle));
      
        // 发送新点的位置(通过信号发送到主线程)
        emit pointGenerated(QPoint(x, y));
        pointCount++;

        QThread::msleep(62);  // 控制绘制速度
    }
  
    qDebug() << "CircleWorker: 画圆完成,共生成" << pointCount << "个点";
    emit workFinished();
}

方形绘制:沿着四条边依次绘制点。

cpp 复制代码
void SquareWorker::doWork(const QPoint &topLeft, int sideLength)
{
    qDebug() << "SquareWorker::doWork 开始 - topLeft:" << topLeft << "sideLength:" << sideLength;
    m_shouldStop = false;
  
    // 检查参数有效性
    if (sideLength <= 0) {
        qWarning() << "SquareWorker: sideLength 无效:" << sideLength;
        emit workFinished();
        return;
    }
  
    // 方形的四个顶点
    QPoint points[4] = {
        topLeft,
        QPoint(topLeft.x() + sideLength, topLeft.y()),
        QPoint(topLeft.x() + sideLength, topLeft.y() + sideLength),
        QPoint(topLeft.x(), topLeft.y() + sideLength)
    };

    int pointCount = 0;
    // 沿着每条边绘制点
    for (int i = 0; i < 4 && !m_shouldStop && !QThread::currentThread()->isInterruptionRequested(); ++i) {
        QPoint start = points[i];
        QPoint end = points[(i + 1) % 4];
      
        // 使用浮点数计算避免整数除法精度问题
        double dx = static_cast<double>(end.x() - start.x()) / sideLength;
        double dy = static_cast<double>(end.y() - start.y()) / sideLength;

        for (int j = 0; j <= sideLength && !m_shouldStop && !QThread::currentThread()->isInterruptionRequested(); ++j) {
            int x = start.x() + static_cast<int>(dx * j);
            int y = start.y() + static_cast<int>(dy * j);
            emit pointGenerated(QPoint(x, y));
            pointCount++;
            QThread::msleep(5);  // 控制绘制速度
        }
    }
  
    qDebug() << "SquareWorker: 画方完成,共生成" << pointCount << "个点";
    emit workFinished();
}

关键点

  • 检查 m_shouldStopQThread::currentThread()->isInterruptionRequested() 以支持优雅停止
  • 使用 emit pointGenerated() 将点发送到主线程
  • 工作完成后发送 workFinished() 信号

7. 线程停止和清理

⚠️ 关键要点:工作完成后必须停止线程!

cpp 复制代码
void CircleThread::onWorkerFinished()
{
    qDebug() << "CircleThread: onWorkerFinished 被调用 - 当前 m_running:" << m_running;
  
    // ⚠️ 停止线程
    if (m_thread && m_thread->isRunning()) {
        qDebug() << "CircleThread: 工作完成,停止线程";
        m_thread->quit();
        if (!m_thread->wait(1000)) {
            qWarning() << "CircleThread: 线程未在1秒内停止,强制终止";
            m_thread->terminate();
            m_thread->wait(500);
        }
    }
  
    // 更新状态
    m_running = false;
    m_workStarted = false;  // 重置工作开始标志
    emit runningChanged();
    emit finished();
  
    // 标记worker为null,因为会被deleteLater删除
    m_worker = nullptr;
    qDebug() << "CircleThread: 工作完成";
}

void CircleThread::stop()
{
    if (!m_thread) {
        return;
    }
  
    // 无论 m_running 是什么,只要线程在运行就停止它
    if (m_thread->isRunning()) {
        qDebug() << "CircleThread: 停止线程 - m_running:" << m_running;
      
        // 通知工作对象停止
        if (m_worker) {
            QMetaObject::invokeMethod(m_worker, "requestStop", Qt::QueuedConnection);
        }
      
        m_thread->requestInterruption();
        m_thread->quit();
        if (!m_thread->wait(1000)) {
            qWarning() << "CircleThread: 线程未在1秒内停止,强制终止";
            m_thread->terminate();
            m_thread->wait(500);
        }
    }
  
    // 更新运行状态
    if (m_running) {
        m_running = false;
        m_workStarted = false;  // 重置工作开始标志
        emit runningChanged();
    }
  
    qDebug() << "CircleThread: 停止画圆线程完成 - isRunning:" << (m_thread ? m_thread->isRunning() : false);
}

关键点

  • onWorkerFinished() 中自动停止线程
  • 使用 quit() 优雅停止,如果超时则使用 terminate() 强制终止
  • 重置所有状态标志
  • Worker 对象通过 deleteLater 自动清理

8. QML 中使用 C++ 类型

注册类型

cpp 复制代码
// 在 main.cpp 中注册
qmlRegisterType<CircleThread>("Multithreading", 1, 0, "CircleThread");
qmlRegisterType<SquareThread>("Multithreading", 1, 0, "SquareThread");

QML 中使用

qml 复制代码
import Multithreading 1.0

ApplicationWindow {
    id: root
  
    // 圆形点集合
    property var circlePoints: []
    // 方形点集合
    property var squarePoints: []
  
    // 画圆线程
    CircleThread {
        id: circleThread
        onCirclePoint: function(pt) {
            root.addCirclePoint(pt);
        }
        onRunningChanged: {
            console.log("Multithreading: 圆形线程 running 状态变化 - running:", running);
        }
        onFinished: {
            console.log("Multithreading: 画圆线程完成 - running:", running);
        }
    }
  
    // 画方线程
    SquareThread {
        id: squareThread
        onSquarePoint: function(pt) {
            root.addSquarePoint(pt);
        }
        onRunningChanged: {
            console.log("Multithreading: 方形线程 running 状态变化 - running:", running);
        }
        onFinished: {
            console.log("Multithreading: 画方线程完成 - running:", running);
        }
    }
  
    // 添加圆形点
    function addCirclePoint(pt) {
        if (!pt) {
            return;
        }
        var newPoints = circlePoints.slice();
        newPoints.push({"x": pt.x, "y": pt.y});
        circlePoints = newPoints;
      
        // 触发 Canvas 重绘
        if (canvas.width > 0 && canvas.height > 0) {
            canvas.requestPaint();
        }
    }
  
    // 添加方形点
    function addSquarePoint(pt) {
        if (!pt) {
            return;
        }
        var newPoints = squarePoints.slice();
        newPoints.push({"x": pt.x, "y": pt.y});
        squarePoints = newPoints;
      
        // 触发 Canvas 重绘
        if (canvas.width > 0 && canvas.height > 0) {
            canvas.requestPaint();
        }
    }
  
    // 启动绘制
    Button {
        text: "开始绘制"
        enabled: !circleThread.running && !squareThread.running
        onClicked: {
            // 清空之前的点
            circlePoints = [];
            squarePoints = [];
            canvas.requestPaint();
          
            // 更新绘制参数
            updateDrawingParameters();
          
            // 启动线程
            Qt.callLater(function() {
                circleThread.start();
                squareThread.start();
            });
        }
    }
}

关键点

  • 使用 import Multithreading 1.0 导入自定义类型
  • 通过 onCirclePoint 等信号处理器接收数据
  • 使用 running 属性控制按钮状态
  • 在 QML 中直接调用 C++ 的 start()stop() 方法

9. Canvas 绘制实现

Canvas 绘制函数

qml 复制代码
Canvas {
    id: canvas
    anchors.fill: parent
  
    onPaint: {
        var ctx = getContext("2d");
        if (!ctx) {
            return;
        }
      
        // 填充白色背景
        ctx.fillStyle = "white";
        ctx.fillRect(0, 0, width, height);
      
        // 绘制圆形点(蓝色)
        ctx.strokeStyle = "blue";
        ctx.lineWidth = 2;
        if (circlePoints.length > 0) {
            ctx.beginPath();
            var pathStarted = false;
            for (var i = 0; i < circlePoints.length; i++) {
                var pt = circlePoints[i];
                // 检查坐标是否在 Canvas 范围内
                if (pt.x >= 0 && pt.x <= width && pt.y >= 0 && pt.y <= height) {
                    if (!pathStarted) {
                        ctx.moveTo(pt.x, pt.y);
                        pathStarted = true;
                    } else {
                        ctx.lineTo(pt.x, pt.y);
                    }
                }
            }
            if (pathStarted) {
                ctx.stroke();
            }
        }
      
        // 绘制方形点(红色)
        ctx.strokeStyle = "red";
        ctx.lineWidth = 2;
        if (squarePoints.length > 0) {
            ctx.beginPath();
            var squarePathStarted = false;
            for (var j = 0; j < squarePoints.length; j++) {
                var sqPt = squarePoints[j];
                if (sqPt.x >= 0 && sqPt.x <= width && sqPt.y >= 0 && sqPt.y <= height) {
                    if (!squarePathStarted) {
                        ctx.moveTo(sqPt.x, sqPt.y);
                        squarePathStarted = true;
                    } else {
                        ctx.lineTo(sqPt.x, sqPt.y);
                    }
                }
            }
            if (squarePathStarted) {
                ctx.stroke();
            }
        }
    }
}

关键点

  • 使用 onPaint 处理绘制逻辑
  • 检查坐标是否在 Canvas 范围内
  • 使用 beginPath()moveTo()lineTo()stroke() 绘制路径
  • 每次收到新点时调用 canvas.requestPaint() 触发重绘

⚠️ 常见陷阱和最佳实践

避免的做法

  1. 在主线程中执行耗时操作

    cpp 复制代码
    // ❌ 错误:在主线程中执行耗时操作
    void CircleThread::start() {
        // 这会阻塞主线程,导致 UI 卡顿
        for (int i = 0; i < 360; ++i) {
            // 计算点...
        }
    }
  2. 直接访问 Worker 对象

    cpp 复制代码
    // ❌ 错误:直接调用 Worker 的方法
    m_worker->doWork(center, radius);  // 这会在主线程中执行!
  3. 忘记移动 Worker 到线程

    cpp 复制代码
    // ❌ 错误:忘记 moveToThread
    m_worker = new CircleWorker();
    // 没有 moveToThread,Worker 仍在主线程中
  4. 使用 AutoConnection 连接信号槽

    cpp 复制代码
    // ❌ 错误:可能使用 DirectConnection(如果对象在同一线程)
    connect(m_worker, &CircleWorker::pointGenerated, 
            this, &CircleThread::onPointGenerated);  // 默认 AutoConnection
  5. 不处理线程停止

    cpp 复制代码
    // ❌ 错误:工作完成后不停止线程
    void CircleThread::onWorkerFinished() {
        m_running = false;
        // 忘记停止线程,导致资源浪费
    }

推荐的做法

  1. 使用 Worker 模式

    cpp 复制代码
    // ✅ 正确:Worker 在工作线程中执行
    m_worker->moveToThread(m_thread);
    QMetaObject::invokeMethod(m_worker, "doWork", Qt::QueuedConnection, ...);
  2. 使用 QueuedConnection

    cpp 复制代码
    // ✅ 正确:确保线程安全
    connect(m_worker, &CircleWorker::pointGenerated, 
            this, &CircleThread::onPointGenerated, 
            Qt::QueuedConnection);
  3. 防止重复调用

    cpp 复制代码
    // ✅ 正确:使用标志防止重复调用
    bool m_workStarted;
    
    void CircleThread::onThreadStarted() {
        if (m_workStarted) {
            return;  // 已调用过,跳过
        }
        m_workStarted = true;
        // 调用 doWork...
    }
  4. 工作完成后停止线程

    cpp 复制代码
    // ✅ 正确:工作完成后停止线程
    void CircleThread::onWorkerFinished() {
        if (m_thread && m_thread->isRunning()) {
            m_thread->quit();
            m_thread->wait(1000);
        }
        m_running = false;
        m_workStarted = false;
    }
  5. 优雅停止工作

    cpp 复制代码
    // ✅ 正确:检查停止标志
    void CircleWorker::doWork(...) {
        for (int i = 0; i < totalPoints && !m_shouldStop && 
             !QThread::currentThread()->isInterruptionRequested(); ++i) {
            // 工作...
        }
    }

💡 完整代码示例

CircleThread.h

cpp 复制代码
#ifndef CIRCLETHREAD_H
#define CIRCLETHREAD_H

#include <QObject>
#include <QThread>
#include <QPoint>
#include <QDebug>
#include <cmath>

// 工作对象:实际在工作线程中运行
class CircleWorker : public QObject
{
    Q_OBJECT

public:
    explicit CircleWorker(QObject *parent = nullptr);
    void requestStop() { m_shouldStop = true; }

public slots:
    void doWork(const QPoint &center, int radius);

signals:
    void pointGenerated(const QPoint &pt);
    void workFinished();

private:
    bool m_shouldStop;
};

// 主对象:始终在主线程,供QML使用
class CircleThread : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QPoint center READ center WRITE setCenter NOTIFY centerChanged)
    Q_PROPERTY(int radius READ radius WRITE setRadius NOTIFY radiusChanged)
    Q_PROPERTY(bool running READ running NOTIFY runningChanged)

public:
    explicit CircleThread(QObject *parent = nullptr);
    ~CircleThread();

    QPoint center() const { return m_center; }
    void setCenter(const QPoint &center);
  
    int radius() const { return m_radius; }
    void setRadius(int radius);
  
    bool running() const { return m_running; }

public slots:
    void start();
    void stop();

signals:
    void circlePoint(const QPoint &pt);
    void centerChanged();
    void radiusChanged();
    void runningChanged();
    void finished();

private slots:
    void onWorkerFinished();
    void onPointGenerated(const QPoint &pt);
    void onThreadStarted();

private:
    QPoint m_center;
    int m_radius;
    QThread *m_thread;
    CircleWorker *m_worker;
    bool m_running;
    bool m_workStarted;  // 防止重复调用 doWork
};

#endif // CIRCLETHREAD_H

CircleThread.cpp(关键部分)

cpp 复制代码
#include "CircleThread.h"
#include <QTimer>

CircleWorker::CircleWorker(QObject *parent)
    : QObject(parent)
    , m_shouldStop(false)
{
}

void CircleWorker::doWork(const QPoint &center, int radius)
{
    m_shouldStop = false;
  
    if (radius <= 0) {
        emit workFinished();
        return;
    }
  
    const int totalPoints = 360;
    double radianIncrement = 2.0 * M_PI / totalPoints;
  
    for (int i = 0; i < totalPoints && !m_shouldStop && 
         !QThread::currentThread()->isInterruptionRequested(); ++i) {
        double circleAngle = i * radianIncrement;
        int x = center.x() + static_cast<int>(radius * cos(circleAngle));
        int y = center.y() + static_cast<int>(radius * sin(circleAngle));
      
        emit pointGenerated(QPoint(x, y));
        QThread::msleep(62);
    }
  
    emit workFinished();
}

CircleThread::CircleThread(QObject *parent)
    : QObject(parent)
    , m_center(0, 0)
    , m_radius(0)
    , m_thread(nullptr)
    , m_worker(nullptr)
    , m_running(false)
    , m_workStarted(false)
{
    m_thread = new QThread(this);
    m_worker = new CircleWorker();
    m_worker->moveToThread(m_thread);
  
    connect(m_worker, &CircleWorker::pointGenerated, 
            this, &CircleThread::onPointGenerated, Qt::QueuedConnection);
    connect(m_worker, &CircleWorker::workFinished, 
            this, &CircleThread::onWorkerFinished, Qt::QueuedConnection);
    connect(m_thread, &QThread::started, 
            this, &CircleThread::onThreadStarted);
    connect(m_thread, &QThread::finished, 
            m_worker, &QObject::deleteLater);
}

void CircleThread::onThreadStarted()
{
    if (m_workStarted) {
        return;  // 防止重复调用
    }
  
    if (!m_worker) {
        return;
    }
  
    m_workStarted = true;
  
    QMetaObject::invokeMethod(m_worker, "doWork", Qt::QueuedConnection,
                             Q_ARG(QPoint, m_center),
                             Q_ARG(int, m_radius));
}

void CircleThread::onWorkerFinished()
{
    if (m_thread && m_thread->isRunning()) {
        m_thread->quit();
        m_thread->wait(1000);
    }
  
    m_running = false;
    m_workStarted = false;
    emit runningChanged();
    emit finished();
    m_worker = nullptr;
}

void CircleThread::stop()
{
    if (!m_thread) {
        return;
    }
  
    if (m_thread->isRunning()) {
        if (m_worker) {
            QMetaObject::invokeMethod(m_worker, "requestStop", Qt::QueuedConnection);
        }
        m_thread->requestInterruption();
        m_thread->quit();
        m_thread->wait(1000);
    }
  
    if (m_running) {
        m_running = false;
        m_workStarted = false;
        emit runningChanged();
    }
}

🔗 相关资源


📝 总结

本文详细介绍了在 HarmonyOS 平台上使用 Qt 实现多线程绘制的完整方案,包括:

  1. 多线程架构:使用 Worker 模式,将工作对象移动到独立线程
  2. 线程安全 :使用 Qt::QueuedConnection 确保线程间通信安全
  3. 防止重复调用 :使用标志位防止 doWork 被重复调用
  4. 线程管理:工作完成后自动停止线程,完善的清理机制
  5. QML 集成:C++ 类型注册到 QML,实现无缝集成
  6. Canvas 绘制:实时绘制线程生成的点,实现动态效果

通过这个项目,我们可以学习到 Qt 多线程编程的最佳实践,以及如何在 HarmonyOS 平台上正确使用 Qt 进行应用开发。

相关推荐
小驰行动派3 小时前
安卓上的极简番茄钟 | 开源
android·开源
国服第二切图仔3 小时前
Qt-for-鸿蒙PC-CheckBox开源鸿蒙开发实践
qt·开源·鸿蒙pc
喵个咪6 小时前
Qt 优雅实现线程安全单例模式(模板化 + 自动清理)
c++·后端·qt
DolphinScheduler社区8 小时前
结项报告完整版 | 为 Apache DolphinScheduler 添加 gRPC 插件
大数据·开源·apache·海豚调度·大数据工作流调度
NocoBase8 小时前
NocoBase 本周更新汇总:新增图表配置的 Al 员工
低代码·开源·资讯
离离茶10 小时前
【笔记1-8】Qt bug记录:QListWidget窗口的浏览模式切换为ListMode后,滚轮滚动速度变慢
笔记·qt·bug
TTGGGFF11 小时前
开源项目分享 : Gitee热榜项目 2025-11-19 日榜
gitee·开源
k***121712 小时前
开源模型应用落地-工具使用篇-Spring AI-Function Call(八)
人工智能·spring·开源
oranglay12 小时前
本地运行开源大语言模型工具全览与对比
人工智能·语言模型·开源