在 QtConcurrent::run 中使用 QSqlDatabase 连接(伪连接池)
Using QSqlDatabase connection in QtConcurrent::run (pseudo connection pooling)
我正试图找到一种有效的方法来处理Qt中的一些数据库查询。场景本质上是,我们有许多事件需要写入数据库。我们不能在写入时阻塞主线程,因此这些写入是在使用QtConcurrent::run
的单独线程中完成的。
现在的问题是,当前每个并发运行都需要创建一个到DB的新连接。我们希望能够简单地创建一次连接并重用它,但Qt文档指出,连接只能在创建它的线程中使用。使用QtConcurrent会造成很大的问题,因为我们不知道我们将在哪个线程中运行。
请注意,我们对并行写入数据库没有兴趣,也就是说,我们可以施加一次只有一个线程使用数据库连接的限制。
有没有任何方法可以使用一个DB连接并仍然使用QtConcurrent?还是像我担心的那样,我们必须使用QThread并实现我们自己的信号,而不是使用并发框架?
答案:正如我所怀疑的那样,这些答案似乎表明这是不可能的。QtConcurrent和DB连接不能很好地配合使用。这真的太糟糕了。我想我会回到创建自己的线程,并使用自定义信号和插槽进行通信。
这篇文章对我使用Qt4.x进行异步数据库访问有很大帮助。我认为在线程中构造一个工作对象并使用排队连接来调用它的插槽,比驱动新的事件并将其发布到线程要好。您可以从这个链接下载示例文件。
我的解决方案是将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.";
}
}
}
根据Qt文档,这是不可能的。QtConcurrent::run((从线程池中获取一个线程,因此您不知道每次将使用哪个线程。我不知道该怎么办。它将占用第一个可用线程。
我真的认为在这种情况下不应该使用QtConcurrent::run((。我认为一个好的方法是使用一个带有QEventLoop的QThread。这非常简单:您只需立即创建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>);
另一个好方法是使用一个QThread池,每个QThread都有自己的连接。如果您需要并发性,那么这可能会很有用。
IIRC正确地说,这个问题要处理的后端比Qt多得多。例如,在过去——现在仍然可能——PostgreSQL要求每个线程都有自己的连接,但MySQL有其他处理线程的方法。只要你遵守后台的规则,一切都可以。
过去,对于PostgreSQL,我创建了一个系统,在这个系统中,我将把一个QSqlQuery推送到一个队列中,另一个线程将清空队列,执行查询,并将sqlresult返回。只要我总是使用相同的"螺纹"连接,这就很好。我在主线程中创建连接并不重要,重要的是何时执行。
QtConcurrent将是这个系统的一个很好的匹配,尽管实际上一次只有一个。不过这会释放主线。
您也许可以创建一个连接队列。当您的函数执行时,它会从队列中提取连接,运行它的查询,并在查询完成后将其添加到队列的末尾。这样可以确保每个线程只使用一个连接。尽管对于每个连接不一定是相同的线程。
同样,这确实取决于后端。查看开发人员文档中关于线程的数据库,并确保遵守这些规则。
- 当套接字连接断开时检测C/C++Unix
- 无法在windows上使用mingw将sqlite3与c连接
- 到连接组件算法的问题(递归)
- QTcpSocket在不阻塞GUI的情况下重新连接到服务器
- 无法在C++中建立与MySQL数据库的连接
- PC中的程序和PHONE中的本机描述应用程序之间的数据连接
- 在Qt Creator中,如何在连接到正在运行的进程后查看控制台输出
- 连接 dockerized 模型和 dockerized 数据库时出现"无法 SQLConnect"错误
- 使用 bfs 解决连接组件问题时得到错误的答案
- 在C++的两个字符串中连接以逗号分隔的唯一值
- 如何修复valgrind启动时的致命错误(与libc6-dbg和libc6-dbg:i386连接)
- C++应用程序 MySQL odbc 数据库连接错误:在引发"otl_tmpl_exception<>"实例后终止调用
- OSX蓝牙打开RFCOMMChannelAsync声称已连接,但未建立连接,并且从未调用过委托
- 在 libcurl 连接池中预创建连接
- 套接字连接"Operation not permitted"错误,甚至使用升压/平发器根.cpp
- libcurl 和 DNS ttl 中的内部连接管理
- QSqlDatabase:如何避免仍在使用'qt_sql_default_connection'和重复连接相关的警告?
- 处理QSqlDatabase连接
- 在 QtConcurrent::run 中使用 QSqlDatabase 连接(伪连接池)
- QSqlTableModel::insertRecord()在使用非默认连接名的QSqlDatabase时失败