Replies: 2 comments 15 replies
-
In theory the What's more problematic is the integration of the two event loops - boost's If you are using Qt6, you could replace If you are using Qt5 (which doesn't have |
Beta Was this translation helpful? Give feedback.
-
Hey, I just want to say that your help in this discussion has helped me refactor my application tremendously, thank you! I have one additional question related to this setup. The application that I am integrating QCoro requires that exceptions occurring in asynchronous contexts must propagate to the main thread and cause the application to close so that the stack trace and exception can be logged and reported. All that to say I am experiencing somewhat strange behavior when using coroutines that throw exceptions. The following code is a refactored version of the above to demonstrate the issue. I expect that calling both error functions (i.e. #include "qcoro/qcoronetworkreply.h"
#include "qcoro/qcorotask.h"
#include <QApplication>
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored \
"-Wdeprecated-enum-enum-conversion" // QSizePolicy seems problematic...
#endif // __clang__
#include <QBoxLayout>
#ifdef __clang__
#pragma clang diagnostic pop
#endif // __clang__
#include <QCoroFuture>
#include <QMainWindow>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QProgressBar>
#include <QPushButton>
#include <boost/asio.hpp>
#include <boost/thread/future.hpp>
#include <chrono>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
using namespace std::chrono_literals;
namespace net = boost::asio; // from <boost/asio.hpp>
/// Convenience function to convert a std::exception_ptr to a boost::exception_ptr
boost::exception_ptr convert_exception_s2b(std::exception_ptr ex)
{
try {
std::rethrow_exception(ex);
} catch(...) {
return boost::current_exception();
}
}
/// Convenience function to convert a boost::exception_ptr to a std::exception_ptr
std::exception_ptr convert_exception_b2s(boost::exception_ptr ex)
{
try {
boost::rethrow_exception(ex);
} catch(...) {
return std::current_exception();
}
}
class AsyncManager
{
public:
AsyncManager()
: _work(net::require(_ioc.get_executor(), net::execution::outstanding_work.tracked))
{
_thread = std::thread([this]() { _ioc.run(); });
}
~AsyncManager()
{
// Gracefully close down.
// Reset the work object, this should block until all work is complete.
_work = net::any_io_executor();
if(!_ioc.stopped())
_ioc.stop();
_thread.join();
}
net::awaitable<std::string> async_op_coro(net::io_context& ioc,
std::chrono::milliseconds timeRequired,
bool simulateException)
{
// Just simulate an asynchronous operation
net::steady_timer timer(ioc);
timer.expires_after(std::chrono::milliseconds(timeRequired));
co_await timer.async_wait(net::use_awaitable);
if(simulateException)
BOOST_THROW_EXCEPTION(std::runtime_error("Async Error Timeout"));
co_return "Async Operation Complete!!!!";
}
boost::future<std::string> asyncOperation(std::chrono::milliseconds timeRequired,
bool simulateException)
{
auto promise = std::make_shared<boost::promise<std::string>>();
auto result = promise->get_future();
// Spawn coroutine that will handle request.
net::co_spawn(_ioc,
async_op_coro(_ioc, timeRequired, simulateException),
[p = std::move(promise)](std::exception_ptr e, auto&& result) {
if(!e) {
p->set_value(result);
} else {
// Convert exception types to maintain exception information
auto bexp = convert_exception_s2b(e);
p->set_exception(bexp);
}
});
return result;
}
private:
/// Thread responsible for servicing the requests
std::thread _thread;
/// I/O context responsible for handling all I/O.
net::io_context _ioc;
/// Executor that will prevent the service from stopping when it
/// runs out of work.
net::any_io_executor _work;
};
template <typename T>
class QFutureAdapter
{
public:
QFutureAdapter(boost::future<T>&& fut) : _fut(std::move(fut)) {}
~QFutureAdapter() {}
auto operator()() -> QFuture<T>
{
auto promise = std::make_shared<QPromise<T>>();
auto future = promise->future();
_fut.then([p = promise](boost::future<T> completeFut) -> void {
std::cout << "then callback..." << std::endl;
if(auto exp = completeFut.get_exception_ptr()) {
auto sexp = convert_exception_b2s(exp);
p->setException(sexp);
} else {
p->addResult(std::move(completeFut.get()));
}
// Always mark as finished.
p->finish();
std::cout << "then callback exit..." << std::endl;
});
return future;
}
private:
boost::future<T> _fut;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow()
{
mPb = new QProgressBar();
mPb->setVisible(false);
mPb->setMinimumWidth(200);
mPb->setMinimum(0);
mPb->setMaximum(0);
mBtn = new QPushButton(tr("Call Error Function 1"));
connect(mBtn, &QPushButton::clicked, this, &MainWindow::coAwaitErrorFunction);
mBtn2 = new QPushButton(tr("Call Error Function 2"));
connect(mBtn2, &QPushButton::clicked, this, &MainWindow::nonCoAwaitErrorFunction);
auto* vbox = new QVBoxLayout();
vbox->addStretch(1);
vbox->addWidget(mPb);
vbox->addWidget(mBtn);
vbox->addWidget(mBtn2);
vbox->addStretch(1);
auto* hbox = new QHBoxLayout();
hbox->addStretch(1);
hbox->addLayout(vbox);
hbox->addStretch(1);
QWidget* w = new QWidget;
w->setLayout(hbox);
setCentralWidget(w);
_async_manager = std::make_shared<AsyncManager>();
}
private Q_SLOTS:
QCoro::Task<void> coAwaitErrorFunction()
{
try {
mPb->setVisible(true);
mBtn->setEnabled(false);
mBtn2->setEnabled(false);
mBtn->setText(tr("Downloading ..."));
// Make request expecting error
auto adapter = QFutureAdapter<std::string>(_async_manager->asyncOperation(300ms, true));
auto result = co_await adapter();
std::cout << "ASYNC RESULT: " << result << std::endl;
co_return;
} catch(const std::exception& e) {
std::cout << "Start error: " << e.what() << std::endl;
mPb->setVisible(false);
mBtn->setEnabled(true);
mBtn2->setEnabled(true);
mBtn->setText(tr("Done, download again"));
// Re throw the exception
throw;
}
}
QCoro::Task<void> nonCoAwaitErrorFunction() { throw std::runtime_error("I'm a bad coroutine"); }
private:
std::shared_ptr<AsyncManager> _async_manager;
QPushButton* mBtn = {};
QPushButton* mBtn2 = {};
QProgressBar* mPb = {};
};
int main(int argc, char** argv)
{
QApplication app(argc, argv);
try {
MainWindow window;
window.showNormal();
return app.exec();
} catch(...) {
std::cout << "CHANCE TO HANDLE GLOBAL UNEXPECTED ERRORS" << std::endl;
}
}
#include "Main.moc" |
Beta Was this translation helpful? Give feedback.
-
I currently have all my applications networking code written with boost::asio and returning futures. For example:
However, this doesn't integrate the best with my Qt UI and I would love to integrate this with QCoro.
If I modify the method above to return a boost coroutine instead:
What would I need to do to
co_await
it inside a QCoro::Task<>?e.g. (barrowed from QCoro networking example)
I did try this and the code doesn't compile because the
boost::asio::awaitable
type doesn't meet the requirements ofQCoro::Task
.Also, I am somewhat concerned that this will not work anyways due to the
restRequest
using aboost::asio::io_context
under the hood. This means the coroutine being awaited above will actually have its IO handled by a thread assigned to theio_context
.Any suggestions appreciated!
Beta Was this translation helpful? Give feedback.
All reactions