Synchronisation mit boost::asio::strand



  • Hallo,

    ich habe gerade etwas mit boost::asio herumgespielt und einen TCP/IP Server gebastelt. Ich stehe jetzt vor dem Problem, dass ich Funktionsaufrufe in dem Thread machen möchte, der den TCP/IP Server erstellt hat (Hauptthread der Anwendung). Das boost::asio::io_service Objekt wird in einem eigenen Thread bedient, damit die Anwendung nicht blockiert wird:

    #include <thread>
    #include <boost/asio.hpp>
    
    class TCPIPServer
    {
       std::thread                      ServiceThread_;
       boost::asio::io_service          Service_;
    
       boost::asio::ip::tcp::socket     Socket_;
       boost::asio::ip::tcp::acceptor   Acceptor_;
    
    public:
       TCPIPServer() :
          Socket_( Service_ ),
          Acceptor_( Service_ )
       {
       }
    
       void start()
       {
          ...
          ServiceThread_ = std::thread( service_driver );
          ...
       }
    
       void stop()
       {
          ...
          Service_.stop();
          ServiceThread_.join();
          Service_.reset();
       }
    
       void service_driver()
       {
          ...
          async_accept();
          Service_.run();
          ...
       }
    
       void async_accept()
       {
          Acceptor_.async_accept( Socket_,
                                  [this]( const boost::system::error_code& ec )
                                  {
                                     if( !ec )
                                     {   // Thread Kontext des Driver Threads
                                         on_connect( /* ... */ );
                                         async_accept();
                                     }
                                  }
         );
       }      
    
       void on_connect( /*...*/ )
       {
          // hier möchte ich in den Hauptthread wechseln, um alle Listener über den
          // Verbindungsaufbau zu informieren.
          Listeners_( /* ... */ );
       }
    };
    

    Sobald sich ein Client verbindet wird der Code ab Zeile 42 ausgeführt, der vom Driver Thread des io_service bedient wird. Da mein GUI Framework Aktualisierungen nur aus dem Anwendungs-Hauptthread zulässt möchte ich ab Zeile 56 wieder in den Hauptthread springen, um dort alle Listener im Hauptthread der Anwendung zu informieren.

    Mit boost::asio::strand können Aufrufe zwischen zwei io_service Objekten synchronisiert werden, die von unterschiedlichen Threads bedient werden. Das Problem dabei ist jetzt auch wieder, wie ich ein zweites io_service Objekt aus dem Hauptthread bediene, denn der run() Aufruf muss ja aus dem Hauptthread erfolgen. Die Konstruktion eines zweiten io_service Objekts mit zugehörigem boost::asio::strand Objekt reicht da nicht aus.

    Unter Windows kann man sich ein Konstrukt mit einem Synchronisationsfenster und PostMessage basteln, aber ich frage mich, ob das nicht auch über boost::asio realisiert werden kann. Hat da jemand eine Idee?



  • Kurz: Nein, das geht leider nicht.

    Lang:
    Das geht nur dann mit boost.asio , wenn dein Hauptthread auch mit einem boost::asio::io_service läuft und genau ein Thread run(..) ausführt. Du hättest dann ein boost::asio::io_service mit zugehörigen Threads für den Server und ein boost::asio::io_service für den Hauptthread, der dein GUI Loop ausführt. Dann können die Aufrufe vom Server-Teil mittels post(..) auf den Hauptthread gemarshallt werden.

    Nun ist es vermutlich nicht ganz einfach, den boost::asio::io_service mit deinem GUI Loop zu "verheiraten" (falls es überhaupt sinnvoll geht). Deshalb ist es wohl besser den Server-Teil über den zum GUI Loop passenden Mechanismus auf den Hauptthread zu marshallen. Ich habe das jeweils so gemacht, dass den Marshalling-Mechanismus (unter Windows QueueUserAPC(..) / PostMessage(..) / PostThreadMessage(..) ...) in einem Dispatcher-Objekt gekapselt wird. Über ein Interface/Template kann man es austauschbar machen, je nach GUI Loop.


Anmelden zum Antworten