diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2016-06-15 06:59:06 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2016-06-15 07:12:13 +0200 |
commit | 77115bab30aa789f9af9fe49006e8747488f8a4c (patch) | |
tree | 5a370c6f25fdce3396d8172b509306f8f501d34f | |
parent | bb70bdcd0eaf72ffc304536267a66c5de5eaf2e9 (diff) | |
download | sink-77115bab30aa789f9af9fe49006e8747488f8a4c.tar.gz sink-77115bab30aa789f9af9fe49006e8747488f8a4c.zip |
Moved thread-boundary crossing to the model.
That way we avoid any unnecessary queuing for the sync API,
and enable fine-tuning in the model code at a later stage.
-rw-r--r-- | common/modelresult.cpp | 31 | ||||
-rw-r--r-- | common/modelresult.h | 7 | ||||
-rw-r--r-- | common/resultprovider.h | 105 | ||||
-rw-r--r-- | common/threadboundary.cpp | 7 | ||||
-rw-r--r-- | tests/clientapitest.cpp | 2 |
5 files changed, 74 insertions, 78 deletions
diff --git a/common/modelresult.cpp b/common/modelresult.cpp index 4a85610..6feca43 100644 --- a/common/modelresult.cpp +++ b/common/modelresult.cpp | |||
@@ -20,9 +20,11 @@ | |||
20 | #include "modelresult.h" | 20 | #include "modelresult.h" |
21 | 21 | ||
22 | #include <QDebug> | 22 | #include <QDebug> |
23 | #include <QThread> | ||
23 | 24 | ||
24 | #include "domain/folder.h" | 25 | #include "domain/folder.h" |
25 | #include "log.h" | 26 | #include "log.h" |
27 | #include "threadboundary.h" | ||
26 | 28 | ||
27 | #undef DEBUG_AREA | 29 | #undef DEBUG_AREA |
28 | #define DEBUG_AREA "client.modelresult" | 30 | #define DEBUG_AREA "client.modelresult" |
@@ -63,12 +65,14 @@ qint64 ModelResult<T, Ptr>::parentId(const Ptr &value) | |||
63 | template <class T, class Ptr> | 65 | template <class T, class Ptr> |
64 | int ModelResult<T, Ptr>::rowCount(const QModelIndex &parent) const | 66 | int ModelResult<T, Ptr>::rowCount(const QModelIndex &parent) const |
65 | { | 67 | { |
68 | Q_ASSERT(QThread::currentThread() == this->thread()); | ||
66 | return mTree[getIdentifier(parent)].size(); | 69 | return mTree[getIdentifier(parent)].size(); |
67 | } | 70 | } |
68 | 71 | ||
69 | template <class T, class Ptr> | 72 | template <class T, class Ptr> |
70 | int ModelResult<T, Ptr>::columnCount(const QModelIndex &parent) const | 73 | int ModelResult<T, Ptr>::columnCount(const QModelIndex &parent) const |
71 | { | 74 | { |
75 | Q_ASSERT(QThread::currentThread() == this->thread()); | ||
72 | return mPropertyColumns.size(); | 76 | return mPropertyColumns.size(); |
73 | } | 77 | } |
74 | 78 | ||
@@ -86,6 +90,7 @@ QVariant ModelResult<T, Ptr>::headerData(int section, Qt::Orientation orientatio | |||
86 | template <class T, class Ptr> | 90 | template <class T, class Ptr> |
87 | QVariant ModelResult<T, Ptr>::data(const QModelIndex &index, int role) const | 91 | QVariant ModelResult<T, Ptr>::data(const QModelIndex &index, int role) const |
88 | { | 92 | { |
93 | Q_ASSERT(QThread::currentThread() == this->thread()); | ||
89 | if (role == DomainObjectRole && index.isValid()) { | 94 | if (role == DomainObjectRole && index.isValid()) { |
90 | Q_ASSERT(mEntities.contains(index.internalId())); | 95 | Q_ASSERT(mEntities.contains(index.internalId())); |
91 | return QVariant::fromValue(mEntities.value(index.internalId())); | 96 | return QVariant::fromValue(mEntities.value(index.internalId())); |
@@ -112,6 +117,7 @@ QVariant ModelResult<T, Ptr>::data(const QModelIndex &index, int role) const | |||
112 | template <class T, class Ptr> | 117 | template <class T, class Ptr> |
113 | QModelIndex ModelResult<T, Ptr>::index(int row, int column, const QModelIndex &parent) const | 118 | QModelIndex ModelResult<T, Ptr>::index(int row, int column, const QModelIndex &parent) const |
114 | { | 119 | { |
120 | Q_ASSERT(QThread::currentThread() == this->thread()); | ||
115 | const auto id = getIdentifier(parent); | 121 | const auto id = getIdentifier(parent); |
116 | const auto list = mTree.value(id); | 122 | const auto list = mTree.value(id); |
117 | if (list.size() > row) { | 123 | if (list.size() > row) { |
@@ -119,12 +125,14 @@ QModelIndex ModelResult<T, Ptr>::index(int row, int column, const QModelIndex &p | |||
119 | return createIndex(row, column, childId); | 125 | return createIndex(row, column, childId); |
120 | } | 126 | } |
121 | Warning() << "Index not available " << row << column << parent; | 127 | Warning() << "Index not available " << row << column << parent; |
128 | Q_ASSERT(false); | ||
122 | return QModelIndex(); | 129 | return QModelIndex(); |
123 | } | 130 | } |
124 | 131 | ||
125 | template <class T, class Ptr> | 132 | template <class T, class Ptr> |
126 | QModelIndex ModelResult<T, Ptr>::createIndexFromId(const qint64 &id) const | 133 | QModelIndex ModelResult<T, Ptr>::createIndexFromId(const qint64 &id) const |
127 | { | 134 | { |
135 | Q_ASSERT(QThread::currentThread() == this->thread()); | ||
128 | if (id == 0) { | 136 | if (id == 0) { |
129 | return QModelIndex(); | 137 | return QModelIndex(); |
130 | } | 138 | } |
@@ -185,6 +193,7 @@ void ModelResult<T, Ptr>::add(const Ptr &value) | |||
185 | } | 193 | } |
186 | if (mEntities.contains(childId)) { | 194 | if (mEntities.contains(childId)) { |
187 | Warning() << "Entity already in model " << value->identifier(); | 195 | Warning() << "Entity already in model " << value->identifier(); |
196 | Q_ASSERT(false); | ||
188 | return; | 197 | return; |
189 | } | 198 | } |
190 | // qDebug() << "Inserting rows " << index << parent; | 199 | // qDebug() << "Inserting rows " << index << parent; |
@@ -216,6 +225,7 @@ void ModelResult<T, Ptr>::remove(const Ptr &value) | |||
216 | template <class T, class Ptr> | 225 | template <class T, class Ptr> |
217 | void ModelResult<T, Ptr>::fetchEntities(const QModelIndex &parent) | 226 | void ModelResult<T, Ptr>::fetchEntities(const QModelIndex &parent) |
218 | { | 227 | { |
228 | Q_ASSERT(QThread::currentThread() == this->thread()); | ||
219 | const auto id = getIdentifier(parent); | 229 | const auto id = getIdentifier(parent); |
220 | mEntityChildrenFetchComplete.remove(id); | 230 | mEntityChildrenFetchComplete.remove(id); |
221 | mEntityChildrenFetched.insert(id); | 231 | mEntityChildrenFetched.insert(id); |
@@ -237,11 +247,26 @@ void ModelResult<T, Ptr>::setFetcher(const std::function<void(const Ptr &parent) | |||
237 | template <class T, class Ptr> | 247 | template <class T, class Ptr> |
238 | void ModelResult<T, Ptr>::setEmitter(const typename Sink::ResultEmitter<Ptr>::Ptr &emitter) | 248 | void ModelResult<T, Ptr>::setEmitter(const typename Sink::ResultEmitter<Ptr>::Ptr &emitter) |
239 | { | 249 | { |
250 | static async::ThreadBoundary threadBoundary; | ||
240 | setFetcher([this](const Ptr &parent) { mEmitter->fetch(parent); }); | 251 | setFetcher([this](const Ptr &parent) { mEmitter->fetch(parent); }); |
241 | emitter->onAdded([this](const Ptr &value) { this->add(value); }); | 252 | |
242 | emitter->onModified([this](const Ptr &value) { this->modify(value); }); | 253 | emitter->onAdded([this](const Ptr &value) { |
243 | emitter->onRemoved([this](const Ptr &value) { this->remove(value); }); | 254 | threadBoundary.callInMainThread([this, value]() { |
255 | add(value); | ||
256 | }); | ||
257 | }); | ||
258 | emitter->onModified([this](const Ptr &value) { | ||
259 | threadBoundary.callInMainThread([this, value]() { | ||
260 | modify(value); | ||
261 | }); | ||
262 | }); | ||
263 | emitter->onRemoved([this](const Ptr &value) { | ||
264 | threadBoundary.callInMainThread([this, value]() { | ||
265 | remove(value); | ||
266 | }); | ||
267 | }); | ||
244 | emitter->onInitialResultSetComplete([this](const Ptr &parent) { | 268 | emitter->onInitialResultSetComplete([this](const Ptr &parent) { |
269 | Trace() << "Initial result set complete"; | ||
245 | const qint64 parentId = parent ? qHash(*parent) : 0; | 270 | const qint64 parentId = parent ? qHash(*parent) : 0; |
246 | const auto parentIndex = createIndexFromId(parentId); | 271 | const auto parentIndex = createIndexFromId(parentId); |
247 | mEntityChildrenFetchComplete.insert(parentId); | 272 | mEntityChildrenFetchComplete.insert(parentId); |
diff --git a/common/modelresult.h b/common/modelresult.h index 0f0c06a..64431da 100644 --- a/common/modelresult.h +++ b/common/modelresult.h | |||
@@ -54,15 +54,14 @@ public: | |||
54 | bool canFetchMore(const QModelIndex &parent) const; | 54 | bool canFetchMore(const QModelIndex &parent) const; |
55 | void fetchMore(const QModelIndex &parent); | 55 | void fetchMore(const QModelIndex &parent); |
56 | 56 | ||
57 | void setFetcher(const std::function<void(const Ptr &parent)> &fetcher); | ||
58 | |||
59 | private: | ||
57 | void add(const Ptr &value); | 60 | void add(const Ptr &value); |
58 | void modify(const Ptr &value); | 61 | void modify(const Ptr &value); |
59 | void remove(const Ptr &value); | 62 | void remove(const Ptr &value); |
60 | |||
61 | void setFetcher(const std::function<void(const Ptr &parent)> &fetcher); | ||
62 | |||
63 | bool childrenFetched(const QModelIndex &) const; | 63 | bool childrenFetched(const QModelIndex &) const; |
64 | 64 | ||
65 | private: | ||
66 | qint64 parentId(const Ptr &value); | 65 | qint64 parentId(const Ptr &value); |
67 | QModelIndex createIndexFromId(const qint64 &id) const; | 66 | QModelIndex createIndexFromId(const qint64 &id) const; |
68 | void fetchEntities(const QModelIndex &parent); | 67 | void fetchEntities(const QModelIndex &parent); |
diff --git a/common/resultprovider.h b/common/resultprovider.h index b7d9272..1c1b0e9 100644 --- a/common/resultprovider.h +++ b/common/resultprovider.h | |||
@@ -20,10 +20,8 @@ | |||
20 | 20 | ||
21 | #pragma once | 21 | #pragma once |
22 | 22 | ||
23 | #include <QThread> | ||
24 | #include <functional> | 23 | #include <functional> |
25 | #include <memory> | 24 | #include <memory> |
26 | #include "threadboundary.h" | ||
27 | #include "resultset.h" | 25 | #include "resultset.h" |
28 | #include "log.h" | 26 | #include "log.h" |
29 | 27 | ||
@@ -75,33 +73,6 @@ private: | |||
75 | template <class T> | 73 | template <class T> |
76 | class ResultProvider : public ResultProviderInterface<T> | 74 | class ResultProvider : public ResultProviderInterface<T> |
77 | { | 75 | { |
78 | private: | ||
79 | void callInMainThreadOnEmitter(void (ResultEmitter<T>::*f)()) | ||
80 | { | ||
81 | // We use the eventloop to call the addHandler directly from the main eventloop. | ||
82 | // That way the result emitter implementation doesn't have to care about threadsafety at all. | ||
83 | // The alternative would be to make all handlers of the emitter threadsafe. | ||
84 | if (auto emitter = mResultEmitter.toStrongRef()) { | ||
85 | auto weakEmitter = mResultEmitter; | ||
86 | // We don't want to keep the emitter alive here, so we only capture a weak reference | ||
87 | emitter->mThreadBoundary.callInMainThread([weakEmitter, f]() { | ||
88 | if (auto strongRef = weakEmitter.toStrongRef()) { | ||
89 | (strongRef.data()->*f)(); | ||
90 | } | ||
91 | }); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | void callInMainThreadOnEmitter(const std::function<void()> &f) | ||
96 | { | ||
97 | // We use the eventloop to call the addHandler directly from the main eventloop. | ||
98 | // That way the result emitter implementation doesn't have to care about threadsafety at all. | ||
99 | // The alternative would be to make all handlers of the emitter threadsafe. | ||
100 | if (auto emitter = mResultEmitter.toStrongRef()) { | ||
101 | emitter->mThreadBoundary.callInMainThread(f); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | public: | 76 | public: |
106 | typedef QSharedPointer<ResultProvider<T>> Ptr; | 77 | typedef QSharedPointer<ResultProvider<T>> Ptr; |
107 | 78 | ||
@@ -112,57 +83,45 @@ public: | |||
112 | // Called from worker thread | 83 | // Called from worker thread |
113 | void add(const T &value) | 84 | void add(const T &value) |
114 | { | 85 | { |
115 | // Because I don't know how to use bind | 86 | if (auto strongRef = mResultEmitter.toStrongRef()) { |
116 | auto weakEmitter = mResultEmitter; | 87 | strongRef->addHandler(value); |
117 | callInMainThreadOnEmitter([weakEmitter, value]() { | 88 | } |
118 | if (auto strongRef = weakEmitter.toStrongRef()) { | ||
119 | strongRef->addHandler(value); | ||
120 | } | ||
121 | }); | ||
122 | } | 89 | } |
123 | 90 | ||
124 | void modify(const T &value) | 91 | void modify(const T &value) |
125 | { | 92 | { |
126 | // Because I don't know how to use bind | 93 | if (auto strongRef = mResultEmitter.toStrongRef()) { |
127 | auto weakEmitter = mResultEmitter; | 94 | strongRef->modifyHandler(value); |
128 | callInMainThreadOnEmitter([weakEmitter, value]() { | 95 | } |
129 | if (auto strongRef = weakEmitter.toStrongRef()) { | ||
130 | strongRef->modifyHandler(value); | ||
131 | } | ||
132 | }); | ||
133 | } | 96 | } |
134 | 97 | ||
135 | void remove(const T &value) | 98 | void remove(const T &value) |
136 | { | 99 | { |
137 | // Because I don't know how to use bind | 100 | if (auto strongRef = mResultEmitter.toStrongRef()) { |
138 | auto weakEmitter = mResultEmitter; | 101 | strongRef->removeHandler(value); |
139 | callInMainThreadOnEmitter([weakEmitter, value]() { | 102 | } |
140 | if (auto strongRef = weakEmitter.toStrongRef()) { | ||
141 | strongRef->removeHandler(value); | ||
142 | } | ||
143 | }); | ||
144 | } | 103 | } |
145 | 104 | ||
146 | void initialResultSetComplete(const T &parent) | 105 | void initialResultSetComplete(const T &parent) |
147 | { | 106 | { |
148 | // Because I don't know how to use bind | 107 | if (auto strongRef = mResultEmitter.toStrongRef()) { |
149 | auto weakEmitter = mResultEmitter; | 108 | strongRef->initialResultSetComplete(parent); |
150 | callInMainThreadOnEmitter([weakEmitter, parent]() { | 109 | } |
151 | if (auto strongRef = weakEmitter.toStrongRef()) { | ||
152 | strongRef->initialResultSetComplete(parent); | ||
153 | } | ||
154 | }); | ||
155 | } | 110 | } |
156 | 111 | ||
157 | // Called from worker thread | 112 | // Called from worker thread |
158 | void complete() | 113 | void complete() |
159 | { | 114 | { |
160 | callInMainThreadOnEmitter(&ResultEmitter<T>::complete); | 115 | if (auto strongRef = mResultEmitter.toStrongRef()) { |
116 | strongRef->complete(); | ||
117 | } | ||
161 | } | 118 | } |
162 | 119 | ||
163 | void clear() | 120 | void clear() |
164 | { | 121 | { |
165 | callInMainThreadOnEmitter(&ResultEmitter<T>::clear); | 122 | if (auto strongRef = mResultEmitter.toStrongRef()) { |
123 | strongRef->clear(); | ||
124 | } | ||
166 | } | 125 | } |
167 | 126 | ||
168 | 127 | ||
@@ -171,7 +130,7 @@ public: | |||
171 | if (!mResultEmitter) { | 130 | if (!mResultEmitter) { |
172 | // We have to go over a separate var and return that, otherwise we'd delete the emitter immediately again | 131 | // We have to go over a separate var and return that, otherwise we'd delete the emitter immediately again |
173 | auto sharedPtr = QSharedPointer<ResultEmitter<T>>(new ResultEmitter<T>, [this](ResultEmitter<T> *emitter) { | 132 | auto sharedPtr = QSharedPointer<ResultEmitter<T>>(new ResultEmitter<T>, [this](ResultEmitter<T> *emitter) { |
174 | mThreadBoundary->callInMainThread([this]() { done(); }); | 133 | done(); |
175 | delete emitter; | 134 | delete emitter; |
176 | }); | 135 | }); |
177 | mResultEmitter = sharedPtr; | 136 | mResultEmitter = sharedPtr; |
@@ -187,7 +146,6 @@ public: | |||
187 | 146 | ||
188 | void onDone(const std::function<void()> &callback) | 147 | void onDone(const std::function<void()> &callback) |
189 | { | 148 | { |
190 | mThreadBoundary = QSharedPointer<async::ThreadBoundary>::create(); | ||
191 | mOnDoneCallback = callback; | 149 | mOnDoneCallback = callback; |
192 | } | 150 | } |
193 | 151 | ||
@@ -205,7 +163,6 @@ public: | |||
205 | private: | 163 | private: |
206 | void done() | 164 | void done() |
207 | { | 165 | { |
208 | qWarning() << "done"; | ||
209 | if (mOnDoneCallback) { | 166 | if (mOnDoneCallback) { |
210 | auto callback = mOnDoneCallback; | 167 | auto callback = mOnDoneCallback; |
211 | mOnDoneCallback = std::function<void()>(); | 168 | mOnDoneCallback = std::function<void()>(); |
@@ -216,7 +173,6 @@ private: | |||
216 | 173 | ||
217 | QWeakPointer<ResultEmitter<T>> mResultEmitter; | 174 | QWeakPointer<ResultEmitter<T>> mResultEmitter; |
218 | std::function<void()> mOnDoneCallback; | 175 | std::function<void()> mOnDoneCallback; |
219 | QSharedPointer<async::ThreadBoundary> mThreadBoundary; | ||
220 | std::function<void(const T &parent)> mFetcher; | 176 | std::function<void(const T &parent)> mFetcher; |
221 | }; | 177 | }; |
222 | 178 | ||
@@ -331,7 +287,6 @@ private: | |||
331 | std::function<void(void)> clearHandler; | 287 | std::function<void(void)> clearHandler; |
332 | 288 | ||
333 | std::function<void(const DomainType &parent)> mFetcher; | 289 | std::function<void(const DomainType &parent)> mFetcher; |
334 | async::ThreadBoundary mThreadBoundary; | ||
335 | }; | 290 | }; |
336 | 291 | ||
337 | template <class DomainType> | 292 | template <class DomainType> |
@@ -350,31 +305,43 @@ public: | |||
350 | emitter->onInitialResultSetComplete([this, ptr](const DomainType &parent) { | 305 | emitter->onInitialResultSetComplete([this, ptr](const DomainType &parent) { |
351 | auto hashValue = qHash(parent); | 306 | auto hashValue = qHash(parent); |
352 | mInitialResultSetInProgress.remove(hashValue, ptr); | 307 | mInitialResultSetInProgress.remove(hashValue, ptr); |
353 | // Normally a parent is only in a single resource, except the toplevel (invalid) parent | 308 | callInitialResultCompleteIfDone(parent); |
354 | if (!mInitialResultSetInProgress.contains(hashValue)) { | ||
355 | this->initialResultSetComplete(parent); | ||
356 | } | ||
357 | }); | 309 | }); |
358 | emitter->onComplete([this]() { this->complete(); }); | 310 | emitter->onComplete([this]() { this->complete(); }); |
359 | emitter->onClear([this]() { this->clear(); }); | 311 | emitter->onClear([this]() { this->clear(); }); |
360 | mEmitter << emitter; | 312 | mEmitter << emitter; |
361 | } | 313 | } |
362 | 314 | ||
315 | void callInitialResultCompleteIfDone(const DomainType &parent) | ||
316 | { | ||
317 | auto hashValue = qHash(parent); | ||
318 | // Normally a parent is only in a single resource, except the toplevel (invalid) parent | ||
319 | if (!mInitialResultSetInProgress.contains(hashValue) && mAllResultsFetched && !mResultEmitted) { | ||
320 | mResultEmitted = true; | ||
321 | this->initialResultSetComplete(parent); | ||
322 | } | ||
323 | } | ||
324 | |||
363 | void fetch(const DomainType &parent) Q_DECL_OVERRIDE | 325 | void fetch(const DomainType &parent) Q_DECL_OVERRIDE |
364 | { | 326 | { |
365 | if (mEmitter.isEmpty()) { | 327 | if (mEmitter.isEmpty()) { |
366 | Trace() << "No child emitters, the result is complete"; | ||
367 | this->initialResultSetComplete(parent); | 328 | this->initialResultSetComplete(parent); |
368 | } else { | 329 | } else { |
330 | mResultEmitted = false; | ||
331 | mAllResultsFetched = false; | ||
369 | for (const auto &emitter : mEmitter) { | 332 | for (const auto &emitter : mEmitter) { |
370 | mInitialResultSetInProgress.insert(qHash(parent), emitter.data()); | 333 | mInitialResultSetInProgress.insert(qHash(parent), emitter.data()); |
371 | emitter->fetch(parent); | 334 | emitter->fetch(parent); |
372 | } | 335 | } |
336 | mAllResultsFetched = true; | ||
337 | callInitialResultCompleteIfDone(parent); | ||
373 | } | 338 | } |
374 | } | 339 | } |
375 | 340 | ||
376 | private: | 341 | private: |
377 | QList<typename ResultEmitter<DomainType>::Ptr> mEmitter; | 342 | QList<typename ResultEmitter<DomainType>::Ptr> mEmitter; |
378 | QMultiMap<qint64, ResultEmitter<DomainType> *> mInitialResultSetInProgress; | 343 | QMultiMap<qint64, ResultEmitter<DomainType> *> mInitialResultSetInProgress; |
344 | bool mAllResultsFetched; | ||
345 | bool mResultEmitted; | ||
379 | }; | 346 | }; |
380 | } | 347 | } |
diff --git a/common/threadboundary.cpp b/common/threadboundary.cpp index 705009b..22864dd 100644 --- a/common/threadboundary.cpp +++ b/common/threadboundary.cpp | |||
@@ -19,6 +19,7 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | #include "threadboundary.h" | 21 | #include "threadboundary.h" |
22 | #include <QThread> | ||
22 | 23 | ||
23 | Q_DECLARE_METATYPE(std::function<void()>); | 24 | Q_DECLARE_METATYPE(std::function<void()>); |
24 | 25 | ||
@@ -39,7 +40,11 @@ void ThreadBoundary::callInMainThread(std::function<void()> f) | |||
39 | * than the target thread is able to execute the function calls. In that case any captures will equally pile up, resulting | 40 | * than the target thread is able to execute the function calls. In that case any captures will equally pile up, resulting |
40 | * in significant memory usage i.e. due to Emitter::addHandler calls that each capture a domain object. | 41 | * in significant memory usage i.e. due to Emitter::addHandler calls that each capture a domain object. |
41 | */ | 42 | */ |
42 | QMetaObject::invokeMethod(this, "runInMainThread", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(std::function<void()>, f)); | 43 | if (QThread::currentThread() == this->thread()) { |
44 | f(); | ||
45 | } else { | ||
46 | QMetaObject::invokeMethod(this, "runInMainThread", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(std::function<void()>, f)); | ||
47 | } | ||
43 | } | 48 | } |
44 | 49 | ||
45 | void ThreadBoundary::runInMainThread(std::function<void()> f) | 50 | void ThreadBoundary::runInMainThread(std::function<void()> f) |
diff --git a/tests/clientapitest.cpp b/tests/clientapitest.cpp index da0b52a..8c5866d 100644 --- a/tests/clientapitest.cpp +++ b/tests/clientapitest.cpp | |||
@@ -255,7 +255,7 @@ private slots: | |||
255 | QCOMPARE(model->rowCount(QModelIndex()), 2); | 255 | QCOMPARE(model->rowCount(QModelIndex()), 2); |
256 | // Ensure children fetched is only emitted once (when all resources are done) | 256 | // Ensure children fetched is only emitted once (when all resources are done) |
257 | QTest::qWait(50); | 257 | QTest::qWait(50); |
258 | QCOMPARE(childrenFetchedCount, 1); | 258 | QVERIFY(childrenFetchedCount <= 1); |
259 | } | 259 | } |
260 | 260 | ||
261 | void testImperativeLoad() | 261 | void testImperativeLoad() |