Смущает, когда boost :: asio :: io_service запускает метод блокировки / разблокировки

Будучи полным новичком в Boost.Asio, меня путают с io_service::run() . Я был бы признателен, если бы кто-нибудь мог объяснить мне, когда этот метод блокирует / разблокирует. В документах говорится:

Функция run() блокируется до тех пор, пока все работы не закончатся, и больше обработчиков не будет отправлено, или пока io_service не будет остановлен.

Несколько streamов могут вызывать функцию run() для настройки пула streamов, из которого io_service может выполнять обработчики. Все streamи, ожидающие в пуле, эквивалентны, и io_service может выбрать любой из них для вызова обработчика.

Нормальный выход из функции run() означает, что объект io_service остановлен (функция io_service stopped() возвращает true). Последующие вызовы run() , run_one() , poll() или poll_one() будут немедленно возвращены, если не будет предыдущего вызова reset() .

Что означает следующее утверждение?

[…] больше обработчиков не будет отправлено […]


При попытке понять поведение io_service::run() я столкнулся с этим примером (пример 3а). Внутри этого я наблюдаю, что io_service->run() блокирует и ждет рабочих заказов.

 // WorkerThread invines io_service->run() void WorkerThread(boost::shared_ptr io_service); void CalculateFib(size_t); boost::shared_ptr io_service( new boost::asio::io_service); boost::shared_ptr work( new boost::asio::io_service::work(*io_service)); // ... boost::thread_group worker_threads; for(int x = 0; x post( boost::bind(CalculateFib, 3)); io_service->post( boost::bind(CalculateFib, 4)); io_service->post( boost::bind(CalculateFib, 5)); work.reset(); worker_threads.join_all(); 

Однако в следующем коде, над которым я работал, клиент подключается с использованием TCP / IP и блоков метода выполнения до тех пор, пока данные не будут получены асинхронно.

 typedef boost::asio::ip::tcp tcp; boost::shared_ptr io_service( new boost::asio::io_service); boost::shared_ptr socket(new tcp::socket(*io_service)); // Connect to 127.0.0.1:9100. tcp::resolver resolver(*io_service); tcp::resolver::query query("127.0.0.1", boost::lexical_cast(9100)); tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); socket->connect(endpoint_iterator->endpoint()); // Just blocks here until a message is received. socket->async_receive(boost::asio::buffer(buf_client, 3000), 0, ClientReceiveEvent); io_service->run(); // Write response. boost::system::error_code ignored_error; std::cout << "Sending message \n"; boost::asio::write(*socket, boost::asio::buffer("some data"), ignored_error); 

Любое объяснение run() , описывающее его поведение в двух приведенных ниже примерах, будет оценено по достоинству.

Фонд

Давайте начнем с упрощенного примера и рассмотрим соответствующие элементы Boost.Asio:

 void handle_async_receive(...) { ... } void print() { ... } ... boost::asio::io_service io_service; boost::asio::ip::tcp::socket socket(io_service); ... io_service.post(&print); // 1 socket.connect(endpoint); // 2 socket.async_receive(buffer, &handle_async_receive); // 3 io_service.post(&print); // 4 io_service.run(); // 5 

Что такое обработчик ?

Обработчик – это не что иное, как обратный вызов. В примере кода есть 3 обработчика:

  • Обработчик print (1).
  • Обработчик handle_async_receive (3).
  • Обработчик print (4).

Несмотря на то, что одна и та же функция print() используется дважды, считается, что каждое использование создает свой собственный однозначно идентифицируемый обработчик. Обработчики могут иметь множество форм и размеров, начиная от базовых функций, таких как выше, до более сложных конструкций, таких как функторы, созданные из boost::bind() и lambdas. Независимо от сложности, обработчик по-прежнему остается не чем иным, как обратным вызовом.

Что такое работа ?

Работа – это некоторая обработка, которую Boost.Asio было предложено сделать от имени кода приложения. Иногда Boost.Asio может начать часть работы, как только об этом расскажут, и в других случаях она может подождать, чтобы выполнить работу в более поздний момент времени. Как только он завершит работу, Boost.Asio сообщит об этом приложению, вызвав предоставленный обработчик .

Boost.Asio гарантирует, что обработчики будут работать только в streamе, который в настоящее время вызывает run() , run_one() , poll() или poll_one() . Это streamи, которые будут работать и обработчики вызовов. Поэтому в приведенном выше примере print() не вызывается, когда он отправляется в io_service (1). Вместо этого он добавляется в io_service и будет вызываться в более поздний момент времени. В этом случае он находится в io_service.run() (5).

Что такое асинхронные операции?

Асинхронная операция создает работу и Boost.Asio будет вызывать обработчик для информирования приложения, когда работа завершена. Асинхронные операции создаются путем вызова функции с именем с префиксом async_ . Эти функции также известны как инициирующие функции .

Асинхронные операции можно разложить на три уникальных шага:

  • Инициирование или информирование связанного с ним io_service который работает, необходимо выполнить. Операция async_receive (3) информирует io_service о необходимости асинхронного чтения данных из сокета, после чего async_receive немедленно возвращается.
  • Выполнение фактической работы. В этом случае, когда socket получает данные, байты считываются и копируются в buffer . Фактическая работа будет выполнена либо:
    • Начальная функция (3), если Boost.Asio может определить, что она не будет блокироваться.
    • Когда приложение явно запускает io_service (5).
  • Вызов handle_async_receive ReadHandler . Вновь обработчики вызываются только в streamах, выполняющих io_service . Таким образом, независимо от того, когда работа выполнена (3 или 5), гарантируется, что handle_async_receive() будет вызван только в io_service.run() (5).

Разделение во времени и пространстве между этими тремя этапами называется инверсией streamа управления. Это одна из сложностей, затрудняющих асинхронное программирование. Однако есть методы, которые могут помочь смягчить это, например, используя сопрограммы .

Что делает io_service.run() ?

Когда stream вызывает io_service.run() , из этого streamа будут вызываться работа и обработчики . В приведенном выше примере io_service.run() (5) будет блокироваться до:

  • Он вызывается и возвращается из обоих обработчиков print , операция получения завершается с успехом или неудачей, и обработчик handle_async_receive был вызван и возвращен.
  • io_service явно остановлен через io_service::stop() .
  • Исключение выбрано из обработчика.

Один потенциальный псевдо-иш-stream можно описать следующим образом:

 создать io_service
 создать сокет
 добавить обработчик печати в io_service (1)
 дождитесь подключения сокета (2)
 добавьте asynchronous запрос на чтение для io_service (3)
 добавить обработчик печати в io_service (4)
 запустите io_service (5)
   есть ли работа или обработчики?
     да, есть 1 работа и 2 обработчика
       у сокетов есть данные?  нет, ничего не делать
       запустить обработчик печати (1)
   есть ли работа или обработчики?
     да, есть 1 работа и 1 обработчик
       у сокетов есть данные?  нет, ничего не делать
       запустить обработчик печати (4)
   есть ли работа или обработчики?
     да, есть 1 работа
       у сокетов есть данные?  нет, продолжайте ждать
   - сокет получает данные -
       socket имеет данные, считывает их в буфер
       добавить обработчик handle_async_receive в io_service
   есть ли работа или обработчики?
     да, есть 1 обработчик
       запустить обработчик handle_async_receive (3)
   есть ли работа или обработчики?
     нет, установите io_service как остановленный и возвращенный 

Обратите внимание, что когда прочитанное закончилось, он добавил другого обработчика в io_service . Эта тонкая деталь является важной особенностью асинхронного программирования. Это позволяет привязывать обработчиков вместе. Например, если handle_async_receive не получил все ожидаемые данные, тогда его реализация может опубликовать другую асинхронную операцию чтения, в результате чего io_service иметь больше работы и, таким образом, не будет возвращаться из io_service.run() .

Обратите внимание, что при io_service из работы io_service приложение должно reset() io_service прежде чем запускать его снова.


Пример Вопрос и пример 3a код

Теперь давайте рассмотрим две части кода, на которые ссылается вопрос.

Код вопроса

socket->async_receive добавляет работу в io_service . Таким образом, io_service->run() будет блокироваться до тех пор, пока операция чтения не завершится с успехом или ошибкой, а ClientReceiveEvent завершит выполнение или выбрасывает исключение.

Пример 3a Код

В надежде, что это будет легче понять, вот небольшой аннотированный пример 3a:

 void CalculateFib(std::size_t n); int main() { boost::asio::io_service io_service; boost::optional work = // '. 1 boost::in_place(boost::ref(io_service)); // .' boost::thread_group worker_threads; // -. for(int x = 0; x < 2; ++x) // : { // '. worker_threads.create_thread( // :- 2 boost::bind(&boost::asio::io_service::run, &io_service) // .' ); // : } // -' io_service.post(boost::bind(CalculateFib, 3)); // '. io_service.post(boost::bind(CalculateFib, 4)); // :- 3 io_service.post(boost::bind(CalculateFib, 5)); // .' work = boost::none; // 4 worker_threads.join_all(); // 5 } 

На высоком уровне программа создаст 2 streamа, которые будут обрабатывать io_service событий io_service (2). Это приводит к простому пулу streamов, который будет вычислять числа Фибоначчи (3).

Основное различие между кодом вопроса и этим кодом заключается в том, что этот код вызывает io_service::run() (2), прежде чем фактическая работа и обработчики будут добавлены в io_service (3). Чтобы предотвратить немедленное возrotation io_service::run() , io_service::work объект io_service::work (1). Этот объект препятствует работе io_service ; поэтому io_service::run() не будет возвращаться в результате отсутствия работы.

Общий stream выглядит следующим образом:

  1. Создайте и добавьте объект io_service::work добавленный в io_service .
  2. Создан пул streamов, который вызывает io_service::run() . Эти рабочие streamи не возвращаются из io_service из-за объекта io_service::work .
  3. Добавьте 3 обработчика, которые вычисляют числа Фибоначчи в io_service и немедленно возвращаются. Рабочие streamи, а не основной stream, могут немедленно запустить эти обработчики.
  4. Удалите объект io_service::work .
  5. Подождите, пока рабочие streamи закончатся. Это произойдет только после завершения всех трех обработчиков, поскольку io_service не имеет обработчиков и не работает.

Код может быть написан по-разному, так же как и io_service код, где обработчики добавляются в io_service , а затем io_service цикл событий io_service . Это устраняет необходимость использования io_service::work и приводит к следующему коду:

 int main() { boost::asio::io_service io_service; io_service.post(boost::bind(CalculateFib, 3)); // '. io_service.post(boost::bind(CalculateFib, 4)); // :- 3 io_service.post(boost::bind(CalculateFib, 5)); // .' boost::thread_group worker_threads; // -. for(int x = 0; x < 2; ++x) // : { // '. worker_threads.create_thread( // :- 2 boost::bind(&boost::asio::io_service::run, &io_service) // .' ); // : } // -' worker_threads.join_all(); // 5 } 

Синхронный и asynchronous

Хотя код в вопросе использует асинхронную операцию, он эффективно работает синхронно, так как он ожидает завершения асинхронной операции:

 socket.async_receive(buffer, handler) io_service.run(); 

эквивалентно:

 boost::asio::error_code error; std::size_t bytes_transferred = socket.receive(buffer, 0, error); handler(error, bytes_transferred); 

Как правило, старайтесь избегать смешивания синхронных и асинхронных операций. Часто это может превратить сложную систему в сложную систему. Этот ответ подчеркивает преимущества асинхронного программирования, некоторые из которых также описаны в документации Boost.Asio.

Чтобы упростить то, как run , подумайте об этом как о сотруднике, который должен обработать кучу бумаги; он берет один лист, делает то, что говорит лист, отбрасывает лист и берет следующий; когда у него заканчиваются листы, он покидает офис. На каждом листе может быть любая инструкция, даже добавив новый лист в кучу. Назад в asio: вы можете дать работу io_service двумя способами: по существу, используя post как в примере, который вы связали, или используя другие объекты, которые внутренне вызывают post в io_service , например, socket и его методы async_* ,

  • Что означает X f ()?
  • Взвешенные случайные числа
  • Загрузка изображения из относительного пути в Windows Forms
  • Как я могу найти определенный элемент в List ?
  • Как добавить значок в приложение, построенное с помощью Eclipse Galileo C и MinGW?
  • В чем разница между QueueUserWorkItem () и BeginInvoke (), для выполнения асинхронной активности без необходимости возврата типов
  • Сглаживание iteratorа
  • Препроцессор C ++: избегать повторения кода списка переменных-членов
  • Исключение: уже есть открытый DataReader, связанный с этим соединением, который должен быть закрыт первым
  • Интересное поведение компилятора с пространствами имен
  • Как получить веб-сервер kestrel для прослушивания запросов, не связанных с localhost?
  • Давайте будем гением компьютера.