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.
- Features
- Getting Started
- Example
- Architecture and Usage Rules
- Public Methods
- Migration and Safety Defaults
- Threading Model
- Safety Considerations
- How It Works
- Important Notes
- Prerequisites
- Basic Example
- Grouping Example
- Support the Project
- ✅ Header-only, zero overhead: No extra dependencies, just copy
core.hinto your project. - ✅ Type-safe registration: Compile-time checks for function signatures using
std::function,if constexpr, andstd::any. - ✅ Task grouping: Run only one task per group (e.g., "network", "file I/O") to serialize access to shared resources.
- ✅ Cooperative stopping: Tasks can check
stopTaskFlag()and exit gracefully, with configurable timeouts. - ✅ Modern C++17: Utilizes
std::atomic,std::bind,enum class,QMetaType, andQSharedPointer. - ✅ Dedicated thread execution: Each registered function runs in its own managed thread, avoiding blocking the main thread.
- ✅ Comprehensive task management: Register, unregister, add, stop, terminate, and query tasks by type, group, or ID.
- ✅ Status queries: Check if a task is registered, idle, added, or active.
Make sure you have Qt 5.12 or later and a C++17 compatible compiler (see Prerequisites for details).
Just copy core.h into your project. It's header‑only!
Or use CMake integration from this repository root:
set(CMAKE_AUTOMOC ON)
add_subdirectory(CoreTemplate)
target_link_libraries(your_target PRIVATE CoreTemplate::CoreTemplate)Install and consume via find_package:
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/your/prefix
cmake --build build
cmake --install buildset(CMAKE_AUTOMOC ON)
find_package(CoreTemplate REQUIRED)
target_link_libraries(your_target PRIVATE CoreTemplate::CoreTemplate)// 1. Init task manager
auto m_pCore = new Core();
// 2. Register a task
m_pCore->registerTask(1, [](int a, int b) -> int {
QThread::msleep(100); // Simulate work
return a + b;
});
// 3. Add it to the queue
m_pCore->addTask(1, 10, 20);
// 4. Handle result
connect(m_pCore, &Core::finishedTask, this, [](TaskId id, TaskType type, const QVariantList& argsList, const QVariant& result) {
Q_UNUSED(id);
Q_UNUSED(type);
Q_UNUSED(argsList);
qDebug() << "Result:" << result.toInt();
});Note: The example above uses heap allocation (
new Core()). You can also create aCoreobject on the stack (e.g.,Core core;) as shown in the Basic Example below.
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.
If you need a minimal non-GUI start, use example/console_main.cpp (ExampleConsoleApp target in CMake).
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,cancelTaskById,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.
The complete listing is defined in the header file core.h. Refer to the source code for detailed documentation.
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.cancelTaskById,cancelTaskByType,cancelTaskByGroup,cancelTasks,cancelAllTasks,cancelTasksByGroup(and backward-compatiblestop...methods): Request graceful (cooperative) cancellation of tasks.terminateTaskById: Requests stop and uses force-termination only when explicitly enabled viasetAllowForceTermination(true).setAllowForceTermination(bool): Enables/disables force-termination path (falseby default).isTaskRegistered,isIdle,isTaskAddedByType,isTaskAddedByGroup: Query task status.groupByTask: Get the group associated with a task type.stopTaskFlag: Returns a thread-local flag pointer for the currently executing task thread; use it inside task code for cooperative stopping.
- Force termination is disabled by default (
allowForceTermination() == false). - Calling
terminateTaskByIdwith force disabled requests cooperative stop only. - To opt in to emergency force termination, call:
core.setAllowForceTermination(true);- Recommended migration: prefer
cancel.../stop...methods and enable force termination only in controlled contexts where abrupt interruption is acceptable.
- 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. - Cancellation is cooperative. A task should periodically check
stopTaskFlag()and exit on request.
- 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 thread-local stop flag retrieved via
Core::stopTaskFlag()to perform graceful shutdowns. - Upon completion (normal or stopped), the task emits
finishedTask. If stop timeout expires, manager attempts force-termination; on failure it emitsstopTimedOutTask, on success it emitsterminatedTask. - 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
CreateThreadon Windows andpthread_create(detached) on 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 thread-local and intended for use within the executing task's thread. - Cancellation vs Termination: prefer
cancelTaskById/stop...methods for cooperative stop requests.terminateTaskByIdis an emergency path that relies on forceful platform APIs and can interrupt execution abruptly. Force termination is disabled by default; enable it explicitly withsetAllowForceTermination(true)only when required. On timeout without actual stop,stopTimedOutTaskis emitted; if force-termination succeeds,terminatedTaskis emitted. - Header-Only: The library is implemented entirely within
core.has an inline/header-only library. - Requirements: Requires Qt 5.12 or later (tested with Qt 6.10.2) and C++17 support.
- Qt 5.12 or later (tested with Qt 6.10.2)
- C++17 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.
Just in case, I don't support wars!
