NicolasR
NicolasR

Reputation: 2312

Boost asio for multiple asynchronous network client operations

I want to use boost::asio (or asio stand alone) to query multiple network devices once a minute for data via asynchronous sockets. For a test I have already implemented a Client class and a console program that does that for one device (without repetition). Something like this:

class MyClient
{
public:
    MyClient(asio::io_service& io_service);

    void GetData(CompletionHandler completionHandler);
}; 

The MyClient::GetData class uses several asynchronous operations internally where the completion of each operation triggers the next until the data are available:

The console program that uses this class works like this:

int main(...)
{
    asio::io_service io_service_;

    MyClient c(io_service_, ...);
    ...
    c.GetData([](std::error_code ec, const FloatVector& values){
        //do something with values
    });

    io_service_.run();
    ...
}

Now I want to use the MyClient class in a GUI program for connecting to >10 devices once a minute but I'm stuck on the overall design.

First I created a thread pool where each thread executes io_service::run() of a single io_service instance.

Now whenever my program wants to read data from the devices it would have a loop over all devices and would have to create an instance of MyClient for each and call the GetData() method.

How does that work together with the io_service now that io_service::run() is executed in the threads of a pool? Can I simply call MyClient::GetData() in the GUI thread because it uses asynchronous operations internally or do I have to call something like io_service::post() ?

Update: My code and console demo follows roughly this example: www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/example/http/client/async_client.cpp

But in a GUI program I don't want to run io_service.run() in the GUI thread. Now assume I have at least one extra thread that executes io_service.run() and a user presses a button that should start the device reading. The final completion handler should store the data in a database and update a graphical display to the user.

Maybe the button handler can simply instantiate MyClient and call GetData() on it and everything works as it should since MyClient knows the io_service and uses it f.e. in async_connect etc.

Does it work like this or am I mistaken here?

Note: at this point my question is not how to handle the data in the completion handler! It is how to correctly get the data in a multithreaded GUI program.

Upvotes: 3

Views: 1199

Answers (2)

Guy Sirton
Guy Sirton

Reputation: 8401

Here's a rough outline of what you need to do:

  • First note that io_service.run() will return immediately if it has no work to do. So depending on your workflow you may need to defer calling it until you actually have the first asynchronous connect queued up. If you look at the example client you'll see that first the client is instantiated which queues up the first operation (an asynchronous resolve in this case) and then io_service.run() is called.
  • So assuming you are starting on a button press what you need to do at this point is schedule the connect or resolve and then spin up a new thread and call io_service.run() from that thread.
  • Once the chain of asynchronous operations completes and you have your data your handler will be called in the context of the new thread you've spun up. This means you have to post a message back to your UI (since typically UI work can only be done on the main thread). E.g. your lambda in your example needs to do some sort of UI post message operation (details depend on which OS/GUI we're talking about here).
  • Then your GUI thread will pick up that message and update whatever UI state you want updated (e.g. display the result)

Upvotes: 1

Nipun Talukdar
Nipun Talukdar

Reputation: 5387

Check this example www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/example/http/client/async_client.cpp . That should help.

If you see if handle_resolve is successful, it calls async_connect which will result in handle_connect to be fired. If handle_connect was called with no error, it writes some data to the connection (async_write) which in turn call async_read_until (on no error) which will fire handle_read_status_line, which may fire handle_read_headers, which may fire handle_read_content.
If you see there is no explicit disconnect as the destructor will do this internally.

Upvotes: 1

Related Questions