【问题标题】:Using QSqlDatabase connection in QtConcurrent::run (pseudo connection pooling)在 QtConcurrent::run 中使用 QSqlDatabase 连接(伪连接池)
【发布时间】:2011-10-16 16:47:25
【问题描述】:

我试图找到一种有效的方法来处理 Qt 中的一些数据库查询。该场景本质上是我们有许多需要写入数据库的事件。我们不能在写入时阻塞主线程,因此这些写入是在使用QtConcurrent::run 的单独线程中完成的。

现在,问题是当前每个并发运行都需要创建到数据库的新连接。我们希望能够简单地创建一次连接并重用它,但是 Qt 文档声明连接只能在创建它的线程中使用。使用 QtConcurrent 会造成很大的问题,因为我们不知道我们将在哪个线程中运行。

请注意,我们对并行写入数据库没有兴趣,也就是说,我们可以施加一次只有一个线程使用数据库连接的限制。

有什么方法可以使用一个 DB 连接并仍然使用 QtConcurrent?或者我们是否必须使用 QThread 并实现我们自己的信号,而不是使用并发框架?


答案:答案似乎表明,正如我所怀疑的那样,它只是无法完成。 QtConcurrent 和 DB 连接不能很好地配合使用。这实在是太糟糕了。我想我会回去创建自己的线程并使用自定义信号和插槽进行通信。

【问题讨论】:

    标签: c++ sql qt asynchronous database-connection


    【解决方案1】:

    我的解决方案是将QtConcurrent 与不会破坏其线程的自定义线程池一起使用。在每个线程的上下文中,我创建了一个专用的QSqlDatabase 连接,并以该线程的名称作为连接的名称,这样每个线程每次需要与数据库通信时都会获取相同的连接。

    设置:

    mThreadPool = new QThreadPool(this);
    // keep threads indefinitely so we don't loose QSqlDatabase connections:
    mThreadPool->setExpiryTimeout(-1);
    mThreadPool->setMaxThreadCount(10); /* equivalent to 10 connections */
    
    qDebug() << "Maximum connection count is "
             << mThreadPool->maxThreadCount();
    

    析构函数:

    // remove the runnables that are not yet started
    mThreadPool->clear();
    // wait for running threads to finish (blocks)
    delete mThreadPool;
    

    返回未来的示例 API 实现,可用于在数据库可用时从数据库中获取数据:

    QFuture<QList<ArticleCategory> *> 
    DatabaseService::fetchAllArticleCategories() const
    {
        return QtConcurrent::run(mThreadPool, 
    &DatabaseService::fetchAllArticleCategoriesWorker, mConnParameters);
    }
    

    请注意,我的解决方案不管理它创建的对象。 调用代码需要管理该内存(上面返回的QList)。

    附带线程工作者函数:

    QList<ArticleCategory> *DatabaseService::fetchAllArticleCategoriesWorker(const DatabaseConnectionParameters &dbconparams)
    {
        try {
            setupThread(dbconparams);
        } catch (exceptions::DatabaseServiceGeneralException &e) {
            qDebug() << e.getMessage();
            return nullptr;
        }
    
        QString threadName = QThread::currentThread()->objectName();
        QSqlDatabase db    = QSqlDatabase::database(threadName, false);
    
        if (db.isValid() && db.open()) {
            QSqlQuery q(db);
            q.setForwardOnly(true);
            // ...
        }
    // else return nullptr
    // ...
    }
    

    如果你注意到了,setupThread 总是在工作线程开始时被调用,它基本上为调用线程准备数据库连接:

    void DatabaseService::setupThread(const DatabaseConnectionParameters &connParams)
    {
        utilities::initializeThreadName(); // just sets a QObject name for this thread
    
        auto thisThreadsName = QThread::currentThread()->objectName();
    
        // check if this thread already has a connection to a database:
        if (!QSqlDatabase::contains(thisThreadsName)) {
            if (!utilities::openDatabaseConnection(thisThreadsName, connParams))
            {
                qDebug() << "Thread"
                        << thisThreadsName
                        << "could not create database connection:"
                        << QSqlDatabase::database(thisThreadsName, false).lastError().text();
            }
            else
            {
                qDebug() << "Thread"
                        << thisThreadsName
                        << "successfully created a database connection.";
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      根据 Qt 文档,这是不可能的。 QtConcurrent::run() 从线程池中获取一个线程,所以你不知道每次会使用哪个线程。我没有办法处理。它将占用第一个可用线程。

      我真的认为你不应该在这种情况下使用 QtConcurrent::run()。我能想到的一个好方法是将 QThread 与 QEventLoop 一起使用。这非常简单:您只需立即创建 QThread 的重新实现,并调用 exec()。像这样的:

      class MyEventLoop : public QThread
      {
      public:
         MyEventLoop() {
            db = QSqlDatbase::addDatabase(<connection_name>);
            // ...
         }
      
         ~MyEventLoop() {
            QSqlDatabase::removeDatabase(<connection_name>);
            // ...
         }
      
         bool event(QEvent* event)
         {
            qDebug("Processing event.");
            return true;
         }
      
         void run() {exec();}
      
      private:
         QSqlDatabase db;
      };
      

      然后您重新实现 QEvent 以包含执行查询所需的任何内容。这只会创建一个线程和一个连接。您不必创建任何队列,也不必处理并发。如果您需要知道查询何时完成,您可以在查询结束时创建信号。要请求新查询,您可以简单地执行以下操作:

      QCoreApplication::postEvent(<pointer_to_thread_instance>, <pointer_to_event_instance>);
      

      另一个好方法是使用一个 QThreads 池,每个都有自己的连接。如果您需要并发性,这无论如何都会很有用。

      【讨论】:

      • 我试图避免使用另一个线程,因为与信号/槽通信机制相比,直接调用线程函数(QtConcurrent 的目的)的能力非常方便。
      • 没有使用信号/插槽。例如,我不明白您为什么说它比 QMetaObject 方便。无论如何,抱歉,我不知道有什么方法可以仅使用 QtConcurrent::run() 使用一个连接。我能想到的最好的事情是它使用的每个线程都有一个连接。希望有人可以提供更多帮助。
      • 您必须添加信号/插槽,因为我需要某种方式与线程通信。使用 QtConcurrent 我调用一个函数并使用 QFuture 来获取结果。这已被信号和插槽所取代。也就是说,这个 DB 线程没有它自己可以做的活动,它需要一些其他线程来告诉它要做什么。
      【解决方案3】:

      IIRC 正确,这个问题比 Qt 处理后端要多得多。例如,在过去——它仍然可能——PostgreSQL 要求每个线程都有自己的连接,但 MySQL 有其他方法来处理线程。只要你遵守后端的规则,一切就OK了。

      过去,对于 PostgreSQL,我创建了一个系统,我将 QSqlQuery 推送到队列中,另一个线程将清空队列,执行查询,并将 sqlresult 传回。只要我总是使用相同的“线程”连接,这很好。我在主线程中创建连接并不重要,重要的是何时执行。

      QtConcurrent 非常适合这个系统,尽管实际上只能一次一个。但它会释放主线程。

      您也许可以创建一个连接队列。当您的函数执行时,它会从队列中拉出一个连接,运行它的查询并在查询完成后将其添加到队列的末尾。这将确保您只为每个线程使用一个连接。虽然不一定每个连接都使用相同的线程。

      同样,这确实取决于后端。检查线程数据库的开发人员文档,并确保您遵守这些规则。

      【讨论】:

      • 由于我们的数据库只是在一个文件中配置,我不能真正依赖任何后端。
      【解决方案4】:

      这篇文章对我帮助很大Asynchronous Database Access with Qt 4.x。 我认为在线程中构造一个工作对象并使用排队连接来调用它的插槽,比驱动新事件并将它们发布到线程更好。您可以从this link.下载示例文件

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-12
        • 1970-01-01
        • 2015-05-02
        • 2013-08-20
        • 2011-03-06
        • 1970-01-01
        相关资源
        最近更新 更多