Skip to the content.

Advanced Usage

The Enqueue method returns a std::future, which allows you to retrieve return values from your asynchronous tasks or handle exceptions thrown within the workers.

Retrieving Results

You can wait for a task to complete and retrieve its result using the returned std::future.

#include <iostream>
#include "cppasyncworker.hpp"

int main() {
    cppasyncworker::WorkerPool pool;

    // Enqueue a task that returns a value
    auto future_result = pool.Enqueue([](int a, int b) {
        return a + b;
    }, 10, 20);

    // Wait for the result and print it
    std::cout << "Result: " << future_result.get() << std::endl;

    return 0;
}

Handling Exceptions

If a task throws an exception, it is caught by the internal std::packaged_task and rethrown when you call .get() on the corresponding std::future.

#include <iostream>
#include <stdexcept>
#include "cppasyncworker.hpp"

int main() {
    cppasyncworker::WorkerPool pool;

    auto future_result = pool.Enqueue([]() {
        throw std::runtime_error("Something went wrong in the background!");
        return 42;
    });

    try {
        // This will rethrow the exception
        future_result.get();
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}

Support for Move-Only Callables

The WorkerPool uses a custom type-erased UniqueTask wrapper, allowing you to enqueue move-only callables (such as lambdas capturing a std::unique_ptr). This enables efficient resource transfer to background threads without unnecessary copying.

#include <iostream>
#include <memory>
#include "cppasyncworker.hpp"

int main() {
    cppasyncworker::WorkerPool pool;

    // Create a unique pointer (move-only)
    auto resource = std::make_unique<int>(100);

    // Enqueue a lambda capturing 'resource' by move
    auto future = pool.Enqueue([r = std::move(resource)]() {
        return *r + 50;
    });

    std::cout << "Resource value processed: " << future.get() << std::endl; // Prints 150

    return 0;
}

Bounded Queues and Backpressure

To prevent unbounded memory consumption under high load (e.g., a fast producer enqueuing tasks much quicker than worker threads can process them), you can configure a maximum capacity for the task queue in the constructor.

When max_queue_size is non-zero and the task queue is full, any subsequent call to Enqueue will block the producer thread until a worker thread executes a task and makes room in the queue.

#include <iostream>
#include "cppasyncworker.hpp"

int main() {
    // 2 worker threads, maximum queue size of 5 tasks
    cppasyncworker::WorkerPool pool(2, 5);

    // The producer can enqueue up to 7 tasks safely (2 executing, 5 waiting in queue)
    for (int i = 0; i < 7; ++i) {
        pool.Enqueue([]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        });
    }

    // This 8th Enqueue will block the main thread until at least one task finishes
    std::cout << "Enqueuing 8th task (might block due to backpressure)..." << std::endl;
    pool.Enqueue([]() {
        std::cout << "8th task executed!" << std::endl;
    });

    pool.Wait();
    return 0;
}

Custom Unhandled Exception Callback

While exceptions are normally propagated through the std::future returned by Enqueue, you might discard or ignore the future for fire-and-forget tasks. To ensure that unhandled exceptions from these tasks do not escape silently, you can register a custom ErrorHandler callback.

The error handler will be executed in the context of the worker thread that caught the exception.

#include <iostream>
#include <stdexcept>
#include "cppasyncworker.hpp"

int main() {
    // Define an error callback
    auto on_error = [](const std::exception_ptr& ex_ptr) {
        try {
            if (ex_ptr) {
                std::rethrow_exception(ex_ptr);
            }
        } catch (const std::exception& e) {
            std::cerr << "[Error Callback] Caught unhandled exception: " << e.what() << std::endl;
        }
    };

    // Instantiate pool with 4 threads, unbounded queue (0), and our error handler
    cppasyncworker::WorkerPool pool(4, 0, on_error);

    // Enqueue a fire-and-forget task that throws (we discard the returned future)
    (void)pool.Enqueue([]() {
        throw std::runtime_error("Fatal background task exception!");
    });

    pool.Wait();
    return 0;
}