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 进行应用开发。

相关推荐
深蓝海拓11 小时前
PySide6从0开始学习的笔记(三) 布局管理器与尺寸策略
笔记·python·qt·学习·pyqt
꧁坚持很酷꧂12 小时前
Windows安装Qt Creator5.15.2(图文详解)
开发语言·windows·qt
IvorySQL12 小时前
PostgreSQL 中的“脏页(Dirty Pages)”是什么?
数据库·postgresql·开源
淼淼76313 小时前
QT表格与数据
开发语言·qt
小灰灰搞电子14 小时前
Qt 实现炫酷锁屏源码分享
开发语言·qt·命令模式
周杰伦_Jay15 小时前
【BGE-M3与主流RAG嵌入模型】知识库嵌入模型对比
人工智能·机器学习·eureka·开源·github
追烽少年x15 小时前
Qt面试题合集(二)
qt
零小陈上(shouhou6668889)15 小时前
YOLOv8+PyQt5玉米病害检测系统(yolov8模型,从图像、视频和摄像头三种路径识别检测)
python·qt·yolo
蓝天智能16 小时前
QT实战:qrc资源动态加载
qt
一见已难忘17 小时前
昇腾加持下的Llama 3.2:开源大模型推理性能1B英文原版与3B中文微调模型实测对比
人工智能·开源·llama·gitcode·昇腾