I finished my earlier work on build environment examples. Illustrating how to do versioning on shared object files right with autotools, qmake, cmake and meson. You can find it here.
Planet maemo: category "feed:43af5b2374081abdd0dbc4ba26a0b54c"
Enough with the political posts!
With asynchronous commands we have typical commands from the Model View ViewModel world that return asynchronously.
Whenever that happens we want result reporting and progress reporting. We basically want something like this in QML:
Item { id: container property ViewModel viewModel: ViewModel {} Connections { target: viewModel.asyncHelloCommand onExecuteProgressed: { progressBar.value = value progressBar.maximumValue = maximum } } ProgressBar { id: progressBar } Button { enabled: viewModel.asyncHelloCommand.canExecute onClicked: viewModel.asyncHelloCommand.execute() } }
How do we do this? First we start with defining a AbstractAsyncCommand (impl. of protected APIs here):
class AbstractAsyncCommand : public AbstractCommand { Q_OBJECT public: AbstractAsyncCommand(QObject *parent=0); Q_INVOKABLE virtual QFuture<void*> executeAsync() = 0; virtual void execute() Q_DECL_OVERRIDE; signals: void executeFinished(void* result); void executeProgressed(int value, int maximum); protected: QSharedPointer<QFutureInterface<void*>> start(); void progress(QSharedPointer<QFutureInterface<void*>> fut, int value, int total); void finish(QSharedPointer<QFutureInterface<void*>> fut, void* result); private: QVector<QSharedPointer<QFutureInterface<void*>>> m_futures; };
After that we provide an implementation:
#include <QThreadPool> #include <QRunnable> #include <MVVM/Commands/AbstractAsyncCommand.h> class AsyncHelloCommand: public AbstractAsyncCommand { Q_OBJECT public: AsyncHelloCommand(QObject *parent=0); bool canExecute() const Q_DECL_OVERRIDE { return true; } QFuture<void*> executeAsync() Q_DECL_OVERRIDE; private: void* executeAsyncTaskFunc(); QSharedPointer<QFutureInterface<void*>> current; QMutex mutex; }; #include "asynchellocommand.h" #include <QtConcurrent/QtConcurrent> AsyncHelloCommand::AsyncHelloCommand(QObject* parent) : AbstractAsyncCommand(parent) { } void* AsyncHelloCommand::executeAsyncTaskFunc() { for (int i=0; i<10; i++) { QThread::sleep(1); qDebug() << "Hello Async!"; mutex.lock(); progress(current, i, 10); mutex.unlock(); } return nullptr; } QFuture<void*> AsyncHelloCommand::executeAsync() { mutex.lock(); current = start(); QFutureWatcher<void*>* watcher = new QFutureWatcher<void*>(this); connect(watcher, &QFutureWatcher<void*>::progressValueChanged, this, [=]{ mutex.lock(); progress(current, watcher->progressValue(), watcher->progressMaximum()); mutex.unlock(); }); connect(watcher, &QFutureWatcher<void*>::finished, this, [=]{ void* result=watcher->result(); mutex.lock(); finish(current, result); mutex.unlock(); watcher->deleteLater(); }); watcher->setFuture(QtConcurrent::run(this, &AsyncHelloCommand::executeAsyncTaskFunc)); QFuture<void*> future = current->future(); mutex.unlock(); return future; }
You can find the complete working example here.
A few days ago I explained how we can do MVVM techniques like ICommand in Qt.
In the .NET XAML world, you have the ICommand, the CompositeCommand and the DelegateCommand. You use these commands to in a declarative way bind them as properties to XAML components like menu items and buttons. You can find an excellent book on this titled Prism 5.0 for WPF.
I’m at home now. I don’t do non-public unpaid work. So let’s blog the example I’m making for him.
Imagine we want an editor that has undo and redo capability. But the operations on the editor are all asynchronous. This implies that also undo and redo are asynchronous operations.
Combining QFuture with QUndoCommand made a lot of sense for us. The undo and the redo methods of the QUndoCommand can also be asynchronous, of course. We wanted to use QFuture without involving threads, because our asynchronosity is done through a process and IPC, and not a thread. It’s the design mistake of QtConcurrent‘s run method, in my opinion. That meant using QFutureInterface instead (which is undocumented, but luckily public – so it’ll remain with us until at least Qt’s 6.y.z releases).
So how do we make a QUndoCommand that has a undo, and that has a redo method that returns a asynchronous QFuture<ResultType>?
We just did that, today. I’m very satisfied with the resulting API and design. It might have helped if QUndoStack would be a QUndoStack<T> and QUndoCommand would have been a QUndoCommand<T> with undo and redo’s return type being T. Just an idea for the Qt 6.y.z developers.
Among the problems we’ll face is that we want asynchronous APIs that are undoable and that we want to switch to read only, undoable editing, non-undoable editing and that QML doesn’t really work well with QFuture. At least not yet. We want an interface that is easy to talk with from QML. Yet we want to switch between complicated behaviors.
Perfection has been reached not when there is nothing left to add, but when there is nothing left to take away.