A modern, header-only C++17 library for running registered tasks in separate threads, with grouping, cooperative cancellation, stop timeouts, and a small callback/event API.
- Header-only core: copy
core.hor link the exported CMake interface target. - No required Qt dependency in core: public API uses standard C++ types.
- Type-safe registration: register free functions, lambdas, functors, non-const member functions, and const member functions.
- Task grouping: only one task per group runs at a time, while tasks from different groups can run concurrently.
- Cooperative cancellation: tasks can check
stopTaskFlag()and exit gracefully. - Event callbacks: observe started, finished, terminated, stop-requested, and stop-timeout events.
- Configurable logging: redirect core debug and warning messages with a standard C++ callback.
- Optional Qt adapter: build a separate QObject/signal bridge without adding Qt to
core.h. - C++17 payloads: task arguments and results are exposed as
std::vector<std::any>andstd::any.
CoreTemplateStd requires a C++17 compiler. No Qt package is required to build or consume the core target.
Copy core.h into your project, or use CMake from this repository:
add_subdirectory(CoreTemplateStd)
target_link_libraries(your_target PRIVATE CoreTemplateStd::CoreTemplateStd)Use directly from GitHub with FetchContent:
include(FetchContent)
FetchContent_Declare(
CoreTemplateStd
GIT_REPOSITORY https://github.com/valeksan/CoreTemplateStd.git
GIT_TAG v0.4.0
)
FetchContent_MakeAvailable(CoreTemplateStd)
target_link_libraries(your_target PRIVATE CoreTemplateStd::CoreTemplateStd)Install and consume via find_package:
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/your/prefix
cmake --build build
cmake --install buildfind_package(CoreTemplateStd REQUIRED)
target_link_libraries(your_target PRIVATE CoreTemplateStd::CoreTemplateStd)Installed package consumers can include the header explicitly:
#include <CoreTemplateStd/core.h>The old CMake package name remains available as a compatibility layer:
find_package(CoreTemplate REQUIRED)
target_link_libraries(your_target PRIVATE CoreTemplate::CoreTemplate)The core target stays Qt-free. If a Qt application wants signal/slot integration, enable the separate adapter target from the source tree:
set(CORETEMPLATE_BUILD_QT_ADAPTER ON)
add_subdirectory(CoreTemplateStd)
target_link_libraries(your_qt_target PRIVATE
CoreTemplateStd::QtAdapter
)Installed packages expose the adapter as an optional component:
find_package(CoreTemplateStd REQUIRED COMPONENTS QtAdapter)
target_link_libraries(your_qt_target PRIVATE CoreTemplateStd::QtAdapter)CoreQtAdapter owns a std-only Core, exposes it through core(), and re-emits core callbacks as Qt signals with QVariantList/QVariant payloads. The adapter supports common scalar and string payload conversions; unsupported std::any payloads become invalid QVariant values. It uses Core::setWakeCallback with queued Qt delivery and one-shot timers, so the GUI example does not need a polling timer.
The Qt Widgets GUI example is also optional:
cmake -S . -B build/qt_gui \
-DCORETEMPLATE_BUILD_EXAMPLE=OFF \
-DCORETEMPLATE_BUILD_QT_GUI_EXAMPLE=ON
cmake --build build/qt_gui --target ExampleQtGuiAppCoreTemplateStd is a breaking std-only branch of the original Qt-oriented API:
- Qt signals such as
finishedTaskare replaced by callback setters such asonFinished. QVariantandQVariantListpayloads are replaced bystd::anyandstd::vector<std::any>.- Qt event-loop delivery is replaced by explicit
processEvents()calls from the managing thread. QObjectownership is removed; constructCoredirectly without a parent object.- Force termination no longer kills worker threads in the
std::threadbackend; non-cooperative tasks receive stop requests and timeout events. - The old CMake names remain as compatibility aliases, but new code should prefer
CoreTemplateStdandCoreTemplateStd::CoreTemplateStd.
#include <CoreTemplateStd/core.h>
#include <any>
#include <chrono>
#include <iostream>
#include <thread>
int main()
{
Core core;
bool finished = false;
core.registerTask(1, [](int a, int b) -> int {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return a + b;
});
core.onFinished([&](const FinishedEvent& event) {
std::cout << "Result: " << std::any_cast<int>(event.result) << '\n';
finished = true;
});
core.addTask(1, 10, 20);
while (!finished) {
core.processEvents();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}See example/console_main.cpp for a minimal runnable example.
The complete API is defined in core.h. The primary methods are:
registerTask: registers a function, lambda, functor, or member function by task type.addTask: queues a registered task with arguments.unregisterTask: removes a registered task type.onStarted,onFinished,onTerminated,onStopRequested,onStopTimedOut: set one callback per event kind.setLogHandler,clearLogHandler: configure or reset the global std-only core log sink.setWakeCallback,clearWakeCallback: configure or reset an owner-loop wake hook for adapters and integrations.processEvents: delivers queued owner-thread events and should be called by the managing thread.cancelTaskById,cancelTaskByType,cancelTaskByGroup,cancelTasks,cancelAllTasks,cancelTasksByGroup: request cooperative cancellation.stopTaskById,stopTaskByType,stopTaskByGroup,stopTasks,stopAllTasks,stopTasksByGroup: compatibility names for the cancellation API.terminateTaskById: requests stop for an active task and reports a timeout if it does not stop cooperatively.setAllowForceTermination,allowForceTermination: retained compatibility switches; the currentstd::threadbackend does not forcibly kill threads.isTaskRegistered,groupByTask,isIdle,isTaskAddedByType,isTaskAddedByGroup: query task state.stopTaskFlag: returns the thread-local stop flag for the currently running task.
Core is designed for one managing thread.
- Create and use a
Coreinstance from one thread. - Call public methods such as
registerTask,addTask, cancellation, and query methods from that same managing thread. - Registered task functions run in worker threads managed by the library.
- Worker completion is queued back to the managing side; call
processEvents()regularly to deliver callbacks and start queued follow-up tasks. - Code running inside a task should not directly call public
Coremethods. Use your application-level message passing to communicate back to the managing thread.
Cancellation is cooperative. A long-running task should periodically check:
if (auto* stop = core.stopTaskFlag(); stop != nullptr && stop->load()) {
return;
}Force termination is disabled by default:
Core core;
core.setAllowForceTermination(true);The portable std::thread backend has no safe standard way to kill a running thread. By default, enabling force termination keeps the API compatible, but non-cooperative tasks still receive a stop request and then a stop-timeout event if they keep running.
For demos or legacy compatibility, a platform-specific unsafe backend can be enabled at configure time:
cmake -S . -B build/unsafe_force \
-DCORETEMPLATE_ENABLE_UNSAFE_FORCE_TERMINATION=ONThis uses native thread termination APIs where available. It can interrupt non-cooperative tasks, but it is unsafe by nature: task destructors and cleanup code may not run. The Qt GUI example enables this option by default so its non-cooperative demo task can be interrupted.
#include <CoreTemplateStd/core.h>
#include <any>
#include <chrono>
#include <iostream>
#include <thread>
int main()
{
Core core;
int finishedCount = 0;
auto work = [](int value) -> int {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return value * 10;
};
core.registerTask(1, work, 1);
core.registerTask(2, work, 1);
core.registerTask(3, work, 2);
core.onFinished([&](const FinishedEvent& event) {
std::cout << "Task " << event.type
<< " result " << std::any_cast<int>(event.result) << '\n';
++finishedCount;
});
core.addTask(1, 10);
core.addTask(2, 20);
core.addTask(3, 30);
while (finishedCount < 3) {
core.processEvents();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}Tasks 1 and 2 share group 1, so they run sequentially. Task 3 belongs to group 2, so it can run concurrently with group 1.
cmake -S . -B build/std_only_check -DCORETEMPLATE_BUILD_TESTS=ON -DCORETEMPLATE_BUILD_EXAMPLE=ON
cmake --build build/std_only_check
ctest --test-dir build/std_only_check --output-on-failure
./build/std_only_check/example/ExampleConsoleApp- The core is header-only and implemented in
core.h. - Core debug and warning messages go to
std::clogandstd::cerrby default, or to the handler configured withCore::setLogHandler. TaskArgsisstd::vector<std::any>.TaskResultisstd::any; avoidtask produces an emptystd::any.std::any_cast<T>is the caller's responsibility when reading callback payloads.- The current backend uses
std::thread; non-cooperative tasks cannot be forcibly killed by standard C++.
If you find this library helpful and wish to support its development, feel free to use the Sponsor button. Any support is voluntary and entirely optional. The library remains free and open-source.