A modern, header-only C++/Qt library for safe and efficient task execution in separate threads, with built-in support for grouping, cooperative stopping, and type-safe registration.
- ✅ Zero overhead: Header-only, no extra dependencies.
- ✅ Type-safe: Compile-time checks for function signatures (thanks to
std::function,if constexpr,std::any). - ✅ Grouping: Run only one task per group (e.g., "network", "file I/O").
- ✅ Cooperative cancellation: Tasks can check
stopTaskFlag()and exit gracefully. - ✅ Modern C++: Uses
std::atomic,std::bind,enum class,QMetaType,QSharedPointer.
// 1. Init task manager
auto m_pCore = new Core();
// 2. Register a task
m_pCore->registerTask(TASK_CALCULATE, [](int a, int b) -> int {
QThread::msleep(100); // Simulate work
return a + b;
});
// 3. Add it to the queue
m_pCore->addTask(TASK_CALCULATE, 10, 20);
// 4. Handle result
connect(m_pCore, &Core::finishedTask, this, [](TaskId id, TaskType type, const QVariant& result) {
qDebug() << "Result:" << result.toInt();
});Just copy core.h into your project. It's header-only!
See example/ directory for a full Qt Widgets app demonstrating all features.
For Qt6, it is preferable to use CMakeLists.txt when opening a project, and if Qt5 then example_app.pro.
- Executes registered functions/lambdas/functors in dedicated threads.
- Supports task grouping (only one task per group runs at a time).
- Provides mechanisms for stopping and terminating tasks.
- Allows querying task status (registered, idle, added by type/group).
IMPORTANT: The Core class is not thread-safe for its public interface methods. To ensure stability:
- All calls to public methods (e.g.,
registerTask,addTask,stopTaskById,terminateTaskById,isTask..., etc.) must originate from the same thread where theCoreobject lives. Typically, this is the main GUI thread. - Functions registered via
registerTaskare executed in their own dedicated threads managed by the library. - Code running inside a registered task function should avoid calling public
Coremethods directly, as this can lead to race conditions and undefined behavior. If a task needs to interact with theCore, it should useQMetaObject::invokeMethodto send a message to the main thread, which then performs the action safely.
registerTask: Registers a function/lambda/functor for later execution by type.addTask: Adds a registered task to the execution queue.unregisterTask: Removes a task type from registration.stopTaskById,stopTaskByType,stopTaskByGroup,stopTasks: Request graceful stop of tasks.terminateTaskById: Forcefully terminates a task by ID.isTaskRegistered,isIdle,isTaskAddedByType,isTaskAddedByGroup: Query task status.groupByTask: Get the group associated with a task type.stopTaskFlag: Get a flag for the current thread to allow cooperative stopping within a task function.
- Main Thread: Hosts the
Coreobject. All public API calls should come from here. - Task Threads: Created internally by the library for each task execution. Registered functions run here.
- Communication: Interaction between Task Threads and Main Thread happens via Qt's signal/slot mechanism (e.g.,
TaskHelper::finished) orQTimerevents scheduled on the main thread (e.g., instopTask).
- Adhering to the single-threaded access rule for the public Core Interface is crucial.
- Be cautious with
QTimer::singleShotandconnectcallbacks if they access shared data outside ofCore's internal structures, especially if those accesses are not synchronized or atomic. - The
Coreclass uses Qt types (QList,QHash,QSharedPointer) which manage their own lifetimes. However, the concurrent access to these types from different threads is avoided by the usage rules.
- An instance of the
Coreclass is created. - Callables are registered with
Core::registerTask(...), assigning them a uniquetaskTypeinteger and optional group and timeout settings. - Tasks are queued for execution using
Core::addTask(taskType, ...args). - The
Coremanages a queue and ensures only one task per group runs at a time. - When a slot opens up (either due to a previous task finishing or because the task belongs to a different group), the
Corestarts the next eligible task in its own thread usingCreateThread(Windows) orpthread_create(Unix-like systems). - The task's associated function executes within the new thread.
- While executing, a task can check a shared stop flag retrieved via
Core::stopTaskFlag()to perform graceful shutdowns. - Upon completion (normal, stopped, or terminated), the task emits a signal (
finishedTask,terminatedTask) back to the main thread where theCorelives. - The
Coreupdates its internal lists of active and queued tasks and proceeds to start the next queued task if applicable.
- Platform Specifics: The library uses
CreateThread/TerminateThreadon Windows andpthread_create/pthread_cancelon Unix-like systems for low-level thread management. - Thread Safety: The
Coreobject itself is designed to be used from the main thread (or a single managing thread). Its methods for adding/stopping tasks are called from the main thread, and its signals are emitted from the main thread context. Access to the internal stop flag (Core::stopTaskFlag()) is intended for use within the executing task's thread. - Header-Only: The library is implemented entirely within
core.has an inline/header-only library. - Requirements: Requires Qt 6.x (specifically tested against 6.10.2) and C++17 support.
- Qt6.x (Tested on version 6.10.2 on Windows 10, but it should theoretically work on Qt5)
- C++20 compatible compiler
#include "core.h"
#include <QApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Core core;
// Define a simple task
auto simpleTask = [](int x) -> int {
qDebug() << "Running simple task with arg:" << x;
return x * 2;
};
// Register the task with type ID 1
core.registerTask(1, simpleTask);
// Connect to the finished signal to handle results
QObject::connect(&core, &Core::finishedTask, [](long id, int type, const QVariantList &args, const QVariant &result) {
qDebug() << "Task finished:" << id << "Type:" << type << "Args:" << args << "Result:" << result;
});
// Add the task for execution with argument 21
core.addTask(1, 21);
// Your application event loop would normally run here.
// For this example, we'll just wait a bit to see the task complete.
QTimer timer;
timer.setSingleShot(true);
timer.start(2000); // Wait 2 seconds
QObject::connect(&timer, &QTimer::timeout, &app, &QApplication::quit);
return app.exec();
}#include "core.h"
#include <QApplication>
#include <QDebug>
#include <QThread>
#include <QTimer>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Core core;
// Define tasks for Group 1 (Resource A)
auto taskForResourceA1 = [](int id) -> int {
qDebug() << "Group 1 Task" << id << "- Starting on thread:" << QThread::currentThread();
QThread::msleep(2000); // Simulate work taking 2 seconds
qDebug() << "Group 1 Task" << id << "- Finished";
return id * 10;
};
auto taskForResourceA2 = [](int id) -> int {
qDebug() << "Group 1 Task" << id << "- Starting on thread:" << QThread::currentThread();
QThread::msleep(1000); // Simulate work taking 1 second
qDebug() << "Group 1 Task" << id << "- Finished";
return id * 20;
};
// Define a task for Group 2 (Resource B) - Can run concurrently with Group 1
auto taskForResourceB = [](int id) -> int {
qDebug() << "Group 2 Task" << id << "- Starting on thread:" << QThread::currentThread();
QThread::msleep(1500); // Simulate work taking 1.5 seconds
qDebug() << "Group 2 Task" << id << "- Finished";
return id * 30;
};
// Register tasks. Group 1 tasks will be serialized.
core.registerTask(1, taskForResourceA1, 1); // Task type 1, Group 1
core.registerTask(2, taskForResourceA2, 1); // Task type 2, Group 1
core.registerTask(3, taskForResourceB, 2); // Task type 3, Group 2
QObject::connect(&core, &Core::finishedTask, [](long id, int type, const QVariantList &args, const QVariant &result) {
qDebug() << "Task completed - ID:" << id << "Type:" << type << "Group:" << args.first().toInt() << "Result:" << result;
});
// Add tasks
qDebug() << "Adding Group 1 Task 1 (ID: 10)";
core.addTask(1, 10); // Will start immediately
qDebug() << "Adding Group 1 Task 2 (ID: 20) - Should wait for Task 1";
core.addTask(2, 20); // Will wait in queue behind Task 1
qDebug() << "Adding Group 2 Task 1 (ID: 30) - Should start immediately, parallel to Group 1 Task 1";
core.addTask(3, 30); // Will start immediately as it's in Group 2
// Wait longer to ensure all tasks complete
QTimer timer;
timer.setSingleShot(true);
timer.start(6000); // Wait 6 seconds
QObject::connect(&timer, &QTimer::timeout, &app, &QApplication::quit);
return app.exec();
}
/* Expected Output (order might vary slightly due to timing):
Adding Group 1 Task 1 (ID: 10)
Adding Group 1 Task 2 (ID: 20) - Should wait for Task 1
Adding Group 2 Task 1 (ID: 30) - Should start immediately, parallel to Group 1 Task 1
Group 1 Task 10 - Starting on thread: QThread(0x...)
Group 2 Task 30 - Starting on thread: QThread(0x...)
Group 2 Task 30 - Finished
Task completed - ID: 2 Type: 3 Group: 2 Result: 900
Group 1 Task 10 - Finished
Task completed - ID: 0 Type: 1 Group: 1 Result: 100
Group 1 Task 20 - Starting on thread: QThread(0x...)
Group 1 Task 20 - Finished
Task completed - ID: 1 Type: 2 Group: 1 Result: 400
*/If you find this library helpful and wish to support its development, feel free to use the Sponsor button. Any support is voluntary and deeply appreciated, but entirely optional. The library remains free and open-source.
P.S. Currently, due to regional restrictions, I don’t have access to international payment systems like Visa or PayPal. However, I do accept support via cryptocurrency (e.g., BTC). If you'd like to contribute this way, please open an issue or contact me directly — I’ll share the wallet details. I hope to gain access to standard financial tools in the future — perhaps after relocating to a place with fewer digital barriers. Until then, crypto is my only option for receiving global support.
