我正在尝试找到一种有效的方法来处理Qt中的一些数据库查询。场景本质上是我们需要写入db的许多事件。我们不能在写入时阻塞主线程,因此这些写操作使用QtConcurrent::run
在不同的线程中完成。
现在,问题是当前每个并发运行都需要创建与DB的新连接。我们希望能够只创建一次连接并重用它,但Qt docs声明连接只能在创建它的线程中使用。使用QtConcurrent使这很成问题,因为我们不知道我们将在哪个线程中运行。
请注意,我们没有兴趣将数据库写入并行,也就是说,我们可以强制限制只有一个线程一次使用数据库连接。
有没有办法使用一个数据库连接,仍然使用QtConcurrent?或者,我担心必须使用QThread并实现我们自己的信令,而不是使用并发框架?
答:答案似乎表明,我怀疑,它只是无法完成。 QtConcurrent和DB连接不能很好地协同工作。这太糟糕了。我想我会回去创建自己的线程并使用自定义信号和插槽进行通信。
根据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>);
另一个好方法是使用QThreads池,每个QThreads都有自己的连接。如果你需要并发,那么无论如何都是有用的。
IIRC正确地说,这个问题比Qt更能处理后端。例如,在过去 - 它仍然可能 - PostgreSQL要求每个线程都有自己的连接,但MySQL有其他方法来处理线程。只要你遵守后端的规则,事情就行了。
在过去,对于PostgreSQL,我创建了一个系统,我将QSqlQuery推送到队列中,另一个线程将清空队列,执行查询,然后传回sqlresult。只要我总是使用相同的“线程”连接,这就没问题了。我在主线程中创建连接并不重要,只有在执行时才重要。
QtConcurrent将是这个系统的一个很好的匹配,但实际上只是一次一个。它会释放主线程。
您可以创建一个连接队列。当您的函数执行时,它会从队列中提取连接,运行它的查询并在查询完成后将其添加到队列的末尾。这将确保您只使用一个连接用于每个线程。虽然每个连接不一定是相同的线程。
同样,这实际上取决于后端。检查有关线程数据库的开发人员文档,并确保遵守这些规则。
这篇文章帮了我很多Asynchronous Database Access with Qt 4.x。我认为在线程中构造一个worker对象并使用排队连接来调用它的插槽,比驱动新事件并将它们发布到线程更好。您可以从this link.下载示例文件
我的解决方案是使用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
总是在worker的开头调用,它基本上为调用线程准备数据库连接:
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.";
}
}
}