为 Qt 工作线程打开和关闭数据库连接的正确方法是啥

Posted

技术标签:

【中文标题】为 Qt 工作线程打开和关闭数据库连接的正确方法是啥【英文标题】:What is the correct way to open and close a database connection for a Qt worker thread为 Qt 工作线程打开和关闭数据库连接的正确方法是什么 【发布时间】:2021-03-26 14:31:09 【问题描述】:

我正在处理一个场景,我们希望在数据库表上异步执行 INSERT/DELETE 语句(这是一个即发即弃的场景)。我打算简单地用相关数据触发一个信号,并让线程的事件循环处理每个信号,类似于以下示例:

Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(signalSource, &SignalSource::dataReady, worker, &Worker::updateMyFancyTable);
workerThread.start();

线程应该在运行时打开自己的数据库连接。我会这样做(当然会增加错误处理):

connect(&workerThread, &QThread::started, worker, &Worker::establishDatabaseConnection);

当停止线程时,它应该完成它的工作然后关闭数据库连接。

据我了解,在线程上调用quit() 将首先处理所有剩余的信号(如果有),然后退出线程的事件循环。

在这种情况下如何正确关闭数据库连接?

    在调用quit()之前是否发出关闭连接的信号? 我是否在发出QThread::finished 后调用的主线程中的插槽中关闭连接? 还有别的吗?

【问题讨论】:

你真的需要关闭连接吗?另外,你不能在线程销毁时关闭它吗? 【参考方案1】:

我认为这个讨论回答了你的一些问题:Stop processing event-queue immediately on QThread.exit()。所以看起来是的,所有的插槽都是在线程退出之前执行的。建议的解决方案是使用 QThread::requestInterruption 并在您的插槽中测试 QThread::currentThread()->isInterruptionRequested() 。请注意,该插槽仍会被多次调用,因此您必须返回,直到队列为空。

在您的情况下,您可以在中断请求后忽略呼叫并发出信号以关闭连接。所以选项1可能是一个解决方案。或者您甚至可以在 isInterruptionRequested() 第一次在您的插槽中返回 true 时关闭连接,但您可能必须处理空事件队列的情况并调用插槽。

选项 2 可能不是一个可行的解决方案,这取决于您如何处理与数据库的连接:通常,连接只能在创建它的线程中使用。 Qt SQL 模块 (https://doc.qt.io/qt-5/threads-modules.html#threads-and-the-sql-module) 就是这种情况。您可以将 finished() 信号连接到后台线程中执行的插槽,然后关闭那里的连接,但我对https://doc.qt.io/qt-5/qthread.html#finished 的理解是这可能是不可能的,因为“当这个信号发出时,事件循环已经停止运行”。奇怪的事情:我测试并在后台线程中调用了插槽......文档说“除了延迟删除事件”,所以在后台线程中调用了 dtor,这将是一个选项。

如果是 QSqlDatabase(或以相同方式工作的连接),我更喜欢手动处理事件队列,以获得更多控制权。例如,创建一个消息队列,等待新请求的信号量并在 QThread 子类中处理这些请求。在 run() 方法中,首先创建连接,在执行期间使用,并在从 run() 方法返回之前关闭。 run() 方法迭代直到请求中断。这使得对象独立,管理自己的数据库连接。这似乎更简单,尤其是当您想同时查询数据库时。

【讨论】:

感谢您的回答。不幸的是,我似乎没有说得足够清楚,我不想中断线程 - 我希望它完成它的工作(直到某个超时),并且只有在它完成后才退出。【参考方案2】:

选项 1 可以完成这项工作,对于您希望在线程处于活动状态时保持数据库连接保持打开状态的情况(您的情况可能支持按查询连接):

Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);

// "SignalSource" is just a placeholder for whatever class sends the signals
connect(this, &SignalSource::dataReady, worker, &Worker::updateMyFancyTable);

// Open the database connection when the thread starts, close it shortly before the end of the thread
connect(&workerThread, &QThread::started, worker, &Worker::establishDatabaseConnection);
connect(signalSource, &SignalSource::endTriggered, worker, &Worker::closeDatabaseConnection);

// Start the thread and do some work
workerThread.start();

那么当线程应该停止时:

// Put an event for closing the database connection in the event loop
emit endTriggered();
// Tell the thread to quit as soon as its event loop is empty
workerThread.quit();
// Wait one minute for the thread to finish
workerThread.wait(60 * 1000);

边缘情况:如果线程在一分钟内没有完成,数据库连接将不会被正确清理。在我们的情况下,这不太可能并且可以接受,在您的情况下可能会有所不同。

附加说明:如果您在多个线程中使用数据库连接,请使用QSqlDatabase::addDatabase 重载,它接受驱动程序名称而不是现有的QSqlDriver*,否则驱动程序将在两个线程中使用,这是不受支持的。

【讨论】:

以上是关于为 Qt 工作线程打开和关闭数据库连接的正确方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

使用完成信号退出 Qt 中的线程并进行 clean_up 的正确方法

Qt 4.7:TCP线程,数据传输导致内存泄漏

pgjava连接关闭

Qt 串口和线程的简单结合(通过子线程操作串口movetothread)

从工作线程更新 QtCharts 的正确方法?

如何正确关闭 websocket