服务器与客户端同步问题: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
相关产品推荐
相关产品推荐

