Неблокировка I/O отличается от синхронной I/O. Истинная операция async I/O обещает инициировать операцию и уведомлять звонящего о завершении, как правило, через какой-то обратный вызов (как описано в Интеграция волокон с асинхронными звонками).
В отличие от этого, неблокирующая операция I/O отказывается начать вообще, если будет необходимо заблокировать, возвращая код ошибки, такой как EWOULDBLOCK
. Операция выполняется только тогда, когда она может завершиться немедленно. В действительности, звонящий должен повторно провести операцию до тех пор, пока она не прекратит возвращение EWOULDBLOCK
.
В классической программе, ориентированной на события, это может быть чем-то вроде головной боли, чтобы использовать неблокирование I/O. В точке, где делается попытка неблокирования I/O, значение возврата EWOULDBLOCK
требует, чтобы звонящий передал контроль обратно к основной петле событий, организовав возврат на следующую итерацию.
Хуже того, неблокирующая операция I/O может частично преуспеть. Это означает, что соответствующая бизнес-логика должна продолжать получать контроль над каждой основной итерацией петли до тех пор, пока не будут обработаны все необходимые данные: дублируемая петля, реализованная как государственный аппарат, управляемый вызовом.
Boost.Fiber может значительно упростить эту проблему. После того, как вы интегрировались с основной петлей приложения, как описано в Соединение Thread с другим главным Loop , ожидание следующей итерации основной волны так же просто, как вызов this_fiber::yield()
.
Для иллюстрации рассмотрим этот API:
class NonblockingAPI {
public:
NonblockingAPI();
int read( std::string & data, std::size_t desired);
...
};
Мы можем построить низкоуровневую обертку около NonblockingAPI::read()
, которая защищает своего звонящего от необходимости иметь дело с EWOULDBLOCK
:
int read_chunk( NonblockingAPI & api, std::string & data, std::size_t desired) {
int error;
while ( EWOULDBLOCK == ( error = api.read( data, desired) ) ) {
boost::this_fiber::yield();
}
return error;
}
Учитывая read_chunk()
, мы можем просто итерировать, пока не получим все желаемые данные:
int read_desired( NonblockingAPI & api, std::string & data, std::size_t desired) {
data.clear();
std::string chunk;
int error = 0;
while ( data.length() < desired &&
( error = read_chunk( api, chunk, desired - data.length() ) ) == 0) {
data.append( chunk);
}
return error;
}
(Из course есть более эффективные способы накопления строковых данных. Это не суть этого примера.)
Наконец, мы можем определить соответствующее исключение:
class IncompleteRead : public std::runtime_error {
public:
IncompleteRead( std::string const& what, std::string const& partial, int ec) :
std::runtime_error( what),
partial_( partial),
ec_( ec) {
}
std::string get_partial() const {
return partial_;
}
int get_errorcode() const {
return ec_;
}
private:
std::string partial_;
int ec_;
};
и написать простую функцию read()
, которая либо возвращает все желаемые данные, либо бросает In completeRead
:
std::string read( NonblockingAPI & api, std::size_t desired) {
std::string data;
int ec( read_desired( api, data, desired) );
if ( 0 == ec || EOF == ec) {
return data;
}
std::ostringstream msg;
msg << "NonblockingAPI::read() error " << ec << " after "
<< data.length() << " of " << desired << " characters";
throw IncompleteRead( msg.str(), data, ec);
}
Как только мы сможем прозрачно ждать следующей итерации с главной крышей, используя This_fiber::yield()
, обычная инкапсуляция Just Works.
Исходный код выше содержится в adapt_nonblocking.cpp.