Question

I am currently calling some methods from an external lib file. Is there a way for these methods to callback functions in my application once they are done as these methods might be running in separate threads? The following diagram shows what I am trying to achieve

enter image description here

I wanted to know what is the best way of sending a message back to the calling application ? Any boost components that might help ?

Was it helpful?

Solution

Update after the edit:

It's not clear what you have. Do you control the thread entry point for the thread started by the external library (this would surprise me)?

Assuming:

  • the library function accepts a callback
  • assuming you don't control the source for the library function, not the thread function started by this library function in a background thread
  • you want to have the callback processed on the original thread

you could have the callback store an record in some kind of queue that you regularly check from the main thread (no busy loops, of course). Use a lock-free queue, or synchronize access to the queue using e.g. a std::mutex.

Update Here's such a queuing version Live on Coliru as well:

#include <thread>
#include <vector>

//////////////////////////////////////////////////////////
// fake external library taking a callback
extern void library_function(int, void(*cb)(int,int));

//////////////////////////////////////////////////////////
// our client code
#include <iostream>
#include <mutex>

void callback_handler(int i, int input)
{
    static std::mutex mx;
    std::lock_guard<std::mutex> lk(mx);
    std::cout << "Callback #" << i << " from task for input " << input << "\n";
}

//////////////////////////////////////////////////////////
// callback queue
#include <deque>
#include <future>

namespace {
    using pending_callback = std::packaged_task<void()>;
    std::deque<pending_callback> callbacks;
    std::mutex callback_mutex;

    int process_pending_callbacks() {
        std::lock_guard<std::mutex> lk(callback_mutex);

        int processed = 0;
        while (!callbacks.empty()) {
            callbacks.front()();
            ++processed;
            callbacks.pop_front();
        }

        return processed;
    }

    void enqueue(pending_callback cb) {
        std::lock_guard<std::mutex> lk(callback_mutex);
        callbacks.push_back(std::move(cb));
    }
}

// this wrapper to "fake" a callback (instead queuing the real
// callback_handler)
void queue_callback(int i, int input)
{
    enqueue(pending_callback(std::bind(callback_handler, i, input)));
}

int main()
{
    // do something with delayed processing:
    library_function(3, queue_callback);
    library_function(5, queue_callback);

    // wait for completion, periodically checking for pending callbacks
    for (
        int still_pending = 3 + 5; 
        still_pending > 0; 
        std::this_thread::sleep_for(std::chrono::milliseconds(10))) // no busy wait
    {
        still_pending -= process_pending_callbacks();
    }
}

//////////////////////////////////////////////////////////
// somewhere, in another library:
void library_function(int some_input, void(*cb)(int,int))
{
    std::thread([=] {
        for (int i = 1; i <= some_input; ++i) {
            std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 5000)); // TODO abolish rand()
            cb(i, some_input);
        }
    }).detach();
}

Typical output:

Callback #1 from task for input 5
Callback #2 from task for input 5
Callback #1 from task for input 3
Callback #3 from task for input 5
Callback #2 from task for input 3
Callback #4 from task for input 5
Callback #5 from task for input 5
Callback #3 from task for input 3

Note that

  • output is interspersed for both worker threads
  • but because the callbacks queue is FIFO, the sequence of callbacks per worker thread is preserved

This is what I thought of, before you edited the question: Live on Coliru

#include <thread>
#include <vector>

extern int library_function(bool);

static std::vector<std::thread> workers; // TODO implement a proper pool

void await_workers()
{
    for(auto& th: workers)
        if (th.joinable()) th.join();
}

template <typename F, typename C>
void do_with_continuation(F f, C continuation)
{
    workers.emplace_back([=] () mutable {
            auto result = f();
            continuation(result);
        });
}

#include <iostream>
#include <mutex>

void callback(int result)
{
    static std::mutex mx;
    std::lock_guard<std::mutex> lk(mx);
    std::cout << "Resulting value from callback " << result << "\n";
}

int main()
{
    // do something with delayed processing:
    do_with_continuation(std::bind(library_function, false), callback);
    do_with_continuation(std::bind(library_function, true),  callback);

    await_workers();
}

// somewhere, in another library:

#include <chrono>
int library_function(bool some_input)
{
    std::this_thread::sleep_for(std::chrono::seconds(some_input? 6 : 3));
    return some_input ? 42 : 0;
}

It will always print the output in the order:

Resulting value from callback 0
Resulting value from callback 42

Notes:

  • make sure you synchronize access to shared state from within such a callback (in this case, std::cout is protected by a lock)
  • you'd want to make a thread pool, instead of an ever-growing vector of (used) threads
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top