You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

服务器与客户端同步问题:gsoap+Qt批量短信发送后数据库记录同步

这个场景我之前做类似批量任务处理时也碰到过,核心问题就是如何追踪所有异步发送线程的完成状态,确保所有操作历史都能正确记录。结合Qt的特性,给你几个实用的解决方案:

方案一:用QThreadPool + QFutureWatcher统一管控任务状态

Qt的QThreadPool天生适合管理大量短周期任务,配合QFutureWatcher可以轻松监听所有任务的完成情况:

  • 把单个手机号的发送逻辑封装成QRunnable子类,比如MessageSenderRunnable,在run()方法里完成消息发送+单条历史记录写入(注意数据库连接要线程安全,每个线程单独创建或克隆连接)
  • 将所有任务提交到QThreadPool,用QFuture<void>追踪每个任务的状态,把这些Future收集到列表中
  • 通过QFutureWatcher监听所有Future的完成状态,它的finished()信号会在所有任务都执行完毕时触发,此时就能确认所有操作历史都已写入数据库

代码示例参考:

// 定义单条消息发送任务
class MessageSenderRunnable : public QRunnable {
public:
    MessageSenderRunnable(const QString& phone, const QString& msg, QSqlDatabase dbTemplate) 
        : m_phone(phone), m_msg(msg), m_dbTemplate(dbTemplate) {}

    void run() override {
        // 1. 执行消息发送逻辑(调用gSOAP接口或短信网关)
        bool sendSuccess = sendMessageToPhone(m_phone, m_msg);

        // 2. 线程安全写入数据库历史
        QSqlDatabase db = QSqlDatabase::cloneDatabase(m_dbTemplate, QString("thread_conn_%1").arg(QThread::currentThreadId()));
        if (db.open()) {
            QSqlQuery query(db);
            query.prepare("INSERT INTO send_history (phone, message, send_time, is_success) VALUES (?, ?, ?, ?)");
            query.addBindValue(m_phone);
            query.addBindValue(m_msg);
            query.addBindValue(QDateTime::currentDateTime());
            query.addBindValue(sendSuccess);
            query.exec();
            db.close();
        }
        QSqlDatabase::removeDatabase(db.connectionName());
    }

private:
    QString m_phone;
    QString m_msg;
    QSqlDatabase m_dbTemplate;
};

// 服务器处理批量请求的方法
void MessageServer::handleBatchRequest(const QList<PhoneMsgPair>& batchData) {
    QSqlDatabase dbTemplate = QSqlDatabase::database(); // 主线程已初始化的数据库连接模板
    QList<QFuture<void>> taskFutures;

    for (const auto& pair : batchData) {
        MessageSenderRunnable* runnable = new MessageSenderRunnable(pair.phone, pair.msg, dbTemplate);
        runnable->setAutoDelete(true);
        // 提交任务并获取追踪Future
        QFuture<void> future = QtConcurrent::run(QThreadPool::globalInstance(), runnable);
        taskFutures.append(future);
    }

    // 监听所有任务完成
    QFutureWatcher<void>* batchWatcher = new QFutureWatcher<void>(this);
    connect(batchWatcher, &QFutureWatcher<void>::finished, this, [this, batchWatcher]() {
        qDebug() << "所有消息发送及历史记录已全部完成!";
        // 这里可以添加批次收尾逻辑,比如给客户端返回处理结果
        batchWatcher->deleteLater();
    });
    batchWatcher->addFutures(taskFutures);
}
方案二:自定义任务管理器追踪QThread完成状态

如果更倾向于用QThread实现,可以做一个轻量的任务管理器,通过信号槽统计完成数:

  • 创建TaskManager类,持有总任务数和已完成任务数的计数器(用QMutex保护,避免多线程冲突)
  • 每个发送线程继承QThread,完成发送和历史写入后发送taskFinished()信号
  • 管理器收到信号后累加完成数,当完成数等于总任务数时,发送allTasksFinished()信号,以此确认所有操作历史已写入

代码示例参考:

// 自定义发送线程类
class MessageSenderThread : public QThread {
    Q_OBJECT
public:
    MessageSenderThread(const QString& phone, const QString& msg, QSqlDatabase dbTemplate, QObject* parent = nullptr)
        : QThread(parent), m_phone(phone), m_msg(msg), m_dbTemplate(dbTemplate) {}

signals:
    void taskFinished();

protected:
    void run() override {
        // 执行消息发送
        bool sendSuccess = sendMessageToPhone(m_phone, m_msg);

        // 写入数据库历史
        QSqlDatabase db = QSqlDatabase::cloneDatabase(m_dbTemplate, QString("thread_conn_%1").arg(QThread::currentThreadId()));
        if (db.open()) {
            QSqlQuery query(db);
            query.prepare("INSERT INTO send_history (phone, message, send_time, is_success) VALUES (?, ?, ?, ?)");
            query.addBindValue(m_phone);
            query.addBindValue(m_msg);
            query.addBindValue(QDateTime::currentDateTime());
            query.addBindValue(sendSuccess);
            query.exec();
            db.close();
        }
        QSqlDatabase::removeDatabase(db.connectionName());

        emit taskFinished();
    }

private:
    QString m_phone;
    QString m_msg;
    QSqlDatabase m_dbTemplate;
};

// 任务管理器类
class BatchTaskManager : public QObject {
    Q_OBJECT
public:
    BatchTaskManager(int totalTasks, QObject* parent = nullptr) : QObject(parent), m_total(totalTasks), m_finished(0) {}

signals:
    void allTasksCompleted();

public slots:
    void onSingleTaskFinished() {
        QMutexLocker locker(&m_counterMutex);
        m_finished++;
        if (m_finished == m_total) {
            emit allTasksCompleted();
        }
    }

private:
    int m_total;
    int m_finished;
    QMutex m_counterMutex;
};

// 服务器处理请求的方法
void MessageServer::handleBatchRequest(const QList<PhoneMsgPair>& batchData) {
    QSqlDatabase dbTemplate = QSqlDatabase::database();
    BatchTaskManager* taskManager = new BatchTaskManager(batchData.size(), this);

    connect(taskManager, &BatchTaskManager::allTasksCompleted, this, [this]() {
        qDebug() << "全部发送任务及历史记录处理完成!";
        // 批次收尾逻辑
    });

    for (const auto& pair : batchData) {
        MessageSenderThread* thread = new MessageSenderThread(pair.phone, pair.msg, dbTemplate, this);
        connect(thread, &MessageSenderThread::taskFinished, taskManager, &BatchTaskManager::onSingleTaskFinished);
        connect(thread, &MessageSenderThread::finished, thread, &MessageSenderThread::deleteLater); // 线程自动销毁
        thread->start();
    }
}
关键注意事项
  • 数据库线程安全:绝对不能多线程共享同一个QSqlDatabase连接,必须每个线程单独创建或克隆模板连接,用完后记得调用removeDatabase清理
  • gSOAP线程安全:如果gSOAP的上下文(soap结构体)是主线程创建的,要确保每个发送任务使用独立的soap上下文,避免多线程冲突
  • 并发限制:10000条数据不要一次性启动所有线程,QThreadPool会自动根据CPU核心数限制并发;如果用QThread,建议自己做任务队列控制并发数,避免资源耗尽
  • 失败处理:示例中默认记录成功/失败状态,实际场景可以添加重试逻辑,确保历史记录能准确反映每一条消息的发送情况

内容的提问来源于stack exchange,提问作者Tazo leladze

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:42:35