为 Qt 工作线程打开和关闭数据库连接的正确方法是什么
What is the correct way to open and close a database connection for a Qt worker thread
我正在研究一个场景,我们想要在数据库 table 上异步执行 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
被发出,它就会被调用?
- 还有别的吗?
我认为这个讨论回答了您的一些问题:。所以看起来是的,所有的槽都在线程退出之前执行。建议的解决方案是在您的插槽中使用 QThread::requestInterruption 并测试 QThread::currentThread()->isInterruptionRequested() 。请注意,插槽仍会被多次调用,因此您必须 return 直到队列为空。
在您的情况下,您可以在中断请求后忽略呼叫并发出信号以关闭连接。所以选项 1 可能是一个解决方案。或者您甚至可以在您的插槽中第一次 isInterruptionRequested() returns true 时关闭连接,但您可能必须处理空事件队列的情况并调用插槽。
选项 2 可能不是一个可行的解决方案,这取决于您如何处理与数据库的连接:通常,连接只能在创建它的线程中使用。 Qt SQL 模块就是这种情况(https://doc.qt.io/qt-5/threads-modules.html#threads-and-the-sql-module). You could connect the finished() signal to a slot executed in the background thread instead, and close the connection there, but my understanding of https://doc.qt.io/qt-5/qthread.html#finished 这可能是不可能的,因为“当这个信号发出时,事件循环已经停止 运行ning”。好奇事情:我测试了并且在后台线程中调用了插槽...文档说“延迟删除事件除外”,因此在后台线程中调用了 dtor,这是一个选项。
如果是 QSqlDatabase(或以相同方式工作的连接),我更喜欢手动处理事件队列,以获得更多控制。例如创建一个消息队列,等待新请求的信号量并在 QThread 子类中处理这些请求。在 运行() 方法中,首先创建连接,在执行期间使用,并在 运行() 方法 returning 之前关闭。 运行() 方法迭代直到请求中断。这使得对象独立,管理它自己的数据库连接。这看起来更简单,特别是当你想并发查询数据库时。
选项 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*
,因为否则驱动程序将被两个线程使用, 不受支持。
我正在研究一个场景,我们想要在数据库 table 上异步执行 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 可能是一个解决方案。或者您甚至可以在您的插槽中第一次 isInterruptionRequested() returns true 时关闭连接,但您可能必须处理空事件队列的情况并调用插槽。
选项 2 可能不是一个可行的解决方案,这取决于您如何处理与数据库的连接:通常,连接只能在创建它的线程中使用。 Qt SQL 模块就是这种情况(https://doc.qt.io/qt-5/threads-modules.html#threads-and-the-sql-module). You could connect the finished() signal to a slot executed in the background thread instead, and close the connection there, but my understanding of https://doc.qt.io/qt-5/qthread.html#finished 这可能是不可能的,因为“当这个信号发出时,事件循环已经停止 运行ning”。好奇事情:我测试了并且在后台线程中调用了插槽...文档说“延迟删除事件除外”,因此在后台线程中调用了 dtor,这是一个选项。
如果是 QSqlDatabase(或以相同方式工作的连接),我更喜欢手动处理事件队列,以获得更多控制。例如创建一个消息队列,等待新请求的信号量并在 QThread 子类中处理这些请求。在 运行() 方法中,首先创建连接,在执行期间使用,并在 运行() 方法 returning 之前关闭。 运行() 方法迭代直到请求中断。这使得对象独立,管理它自己的数据库连接。这看起来更简单,特别是当你想并发查询数据库时。
选项 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*
,因为否则驱动程序将被两个线程使用, 不受支持。