/* Copyright (C) 2023 The Qt Company Ltd. * * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 */ #pragma once #include "asynctask_p.h" #include #include namespace QLicenseService { template class AsyncTask { public: enum class Status { Error = -1, NotStarted = 0, Starting, Running, Canceled, Finished }; enum class Properties { None = 0, Cancelable = 1 }; AsyncTask() : d(new AsyncTaskPrivate()) , m_properties(Properties::None) , m_status(Status::NotStarted) , m_canceled(false) , m_errorCode(0) {} virtual ~AsyncTask() { if (!d->m_future.valid()) return; if (status() == Status::Running) cancel(); waitForFinished(); delete d; }; bool start() { // don't allow starting already started task again if (d->m_future.valid()) { setError("Task is already started"); return false; } updateAndNotifyStatus(Status::Starting); try { d->m_future = std::async(std::launch::async, &AsyncTask::run, this); } catch (const std::system_error &e) { setError("Caught exception: " + std::string(e.what()), e.code().value()); updateAndNotifyStatus(Status::Error); return false; } catch (const std::bad_alloc &e) { setError("Caught exception: " + std::string(e.what())); updateAndNotifyStatus(Status::Error); return false; } catch (...) { setError("Caught unknown exception"); updateAndNotifyStatus(Status::Error); return false; } return true; } bool cancel() { if (!static_cast(m_properties & Properties::Cancelable)) { setError("Canceling is not supported by current task"); return false; } if (status() != Status::Running) { setError("Task is not running"); return false; } const std::lock_guard _(m_mutex); m_canceled = true; return true; } Status status() const { const std::lock_guard _(m_mutex); return m_status; } T result() { return d->result(); } int error() const { const std::lock_guard _(m_mutex); return m_errorCode; } std::string errorString() const { const std::lock_guard _(m_mutex); return m_errorString; } bool waitForStarted() { std::unique_lock lock(m_mutex); if (!d->m_future.valid()) { setError("Task is not started"); return false; } if (m_status != Status::Starting) { setError("Unexpected status"); return false; } m_cv.wait(lock); return (m_status == Status::Running); } bool waitForFinished(int msecs = -1) { if (!d->m_future.valid()) { setError("Task is not started"); return false; } if (msecs <= 0) { d->m_future.wait(); return true; } const std::future_status status = d->m_future.wait_for(std::chrono::milliseconds(msecs)); if (status == std::future_status::timeout) setError("Timeout while waiting"); return (status == std::future_status::ready); } public: friend inline Properties operator|(Properties lhs, Properties rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } friend inline Properties operator&(Properties lhs, Properties rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } friend inline Properties &operator|=(Properties &lhs, Properties rhs) { return lhs = static_cast(static_cast(lhs) | static_cast(rhs)); } protected: virtual T run() = 0; void updateAndNotifyStatus(const Status &status) { const std::lock_guard _(m_mutex); m_status = status; m_cv.notify_all(); } bool isCancelRequested() { const std::lock_guard _(m_mutex); return m_canceled; } void setError(const std::string &message, int code = 0) { const std::lock_guard _(m_mutex); m_errorCode = code; m_errorString = message; } protected: Properties m_properties; private: AsyncTaskPrivate *d; Status m_status; bool m_canceled; std::string m_errorString; int m_errorCode; mutable std::mutex m_mutex; std::condition_variable m_cv; }; } // namespace QLicenseService