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

时间:2021-03-26 14:31:09

标签: qt qthread qsqldatabase

我正在研究一个场景,我们希望在数据库表上异步执行 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() 将首先处理所有剩余的信号(如果有),然后退出线程的事件循环。

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

  1. 我是否在调用 quit() 之前发出关闭连接的信号?
  2. 我是否在主线程中的一个插槽中关闭连接,一旦 QThread::finished 被发出,它就会被调用?
  3. 还有别的吗?

2 个答案:

答案 0 :(得分: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() 方法迭代直到请求中断。这使得对象独立,管理自己的数据库连接。这看起来更简单,特别是当您想要并发查询数据库时。

答案 1 :(得分:0)

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

http.get({ 
    host: '127.0.0.1',
    port: 443,
    path: '/books?author=spongebob',
    auth: 'user:p@ssword#'
 }, resp => {
    let data;

    resp.on('data', chunk => {
        data += chunk;
    });

    resp.on('end', () => console.log(data));
}).on('error', err => console.log(err));

然后当线程应该停止时:

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 重载,因为否则将在两个线程中使用该驱动程序,不支持。