Здравствуйте, flаt, Вы писали:
F>Можно подумать, от auto client = std::make_shared<MyClient>() что-то изменится в понимании примера.
ну, значит автору того хэлоуворда есть еще куда учиться бессмысленности...
надеюсь, в следующий раз, в хэлоуворд, он обязательно впишет ваш, бессмысленный для хэлоуворда, пример
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
PM>Проблемы будут с asio, как и с какой-то другой библиотекой асинхронности, потому что асинхронность это сложно. И часто асинхронность используют еще и многопоточной среде, что только добавляет сложности.
В asio сама реализация асинхронности занимает наименьшую часть кода. И это самая простая часть кода. Всё остальное-это
врайперы, сводящие воедино кучи интерфейсов и куч ОСей.
PM>Может быть я ошибаюсь, и есть альтернативы Asio? Я немного работал с libuv, там тоже не все просто.
Если нет требоавания кроссплатформа, то писать на нативном API ОС, будет проще, чем разбираться с
фантазиями создателей asio.
Здравствуйте, uzhas, Вы писали:
J>>Далее, зачем обязательно убивать сокет явно? Достаточно хранить его в любом умном указателе и передавать явно в континуацию. Тогда при закрытии сокета в континуацию придет operation_aborted — ты в результате просто не поставишь в очередь очередную континуацию (которая держала бы сокет на плаву), и сокет мирно помрет сам вместе с указателем.
U>работа с внешними ресурсами часто требует полного и явного контроля. если говорить о сокетах, то иногда нужно явно освободить порт.
socket.close() его _уже_ освободил, не?
U>полагаться на shared_ptr я бы не стал из-за непредсказуемости кол-ва владельцев (более точно из-за непредсказуемости момента уничтожения захваченного объекта)
порт уже будет освобожден при помощи socket.close(). shared_ptr просто удалит неактивный объект, когда asio дернет все продолжения, которые с ним связаны (c operation_aborted).
Здравствуйте, fdn721, Вы писали:
F>Здравствуйте, jazzer, Вы писали:
J>>Здравствуйте, uzhas, Вы писали:
U>>>полагаться на shared_ptr я бы не стал из-за непредсказуемости кол-ва владельцев (более точно из-за непредсказуемости момента уничтожения захваченного объекта)
J>>порт уже будет освобожден при помощи socket.close(). shared_ptr просто удалит неактивный объект, когда asio дернет все продолжения, которые с ним связаны (c operation_aborted).
F>Всё маленько сложнее, хэндлеры(продолжения) могут в момент socket.close() исполняться.
asio тут ни при чем вообще.
У тебя просто некорректная многопоточная работа с разделяемым объектом, не защищенным мьютексом.
Эта проблема у тебя будет абсолютно везде, хоть с asio, хоть с рукопашными pthreads.
Нельзя допускать, что в одном потоке ты пишешь в сокет (или куда угодно еще, хоть в memory mapped segment), а в другом — его (сокет/сегмент) убиваешь, и это может происходить одновременно.
В принципе нельзя. Надо обязательно защищать, никаких тут других вариантов нет, это же классические гонки по граблям.
F>Если после socket.close() сразу разрывать связь MyClient с остальной программой (освободить m_dataProcessor), то мы нарвёмся на очень нехорошую ситуацию.
А зачем ее сразу разрывать, если это просто указатель? Просто дождаться нормальной смерти MyClient никак нельзя?
F>Мы всё равно можем получить вызов m_dataProcessor->ProcessData(...) когда считаем что соединение уже завершено.
Это называется data race. Asio тут ни при чем.
F>PS В общем после попытки использовать asio осталось стойкое впечатление что я что-то делаю не так. Но ни где не написано как надо делать. Всё больше советов от теоретиков, типа используй shared_from_this и всё получится.
Я, как практик asio, имею в своем MyClient конечный автомат (обычный enum). И когда мни приходит в голову прибить клиента и его многочисленные сокеты/таймеры (например, по таймеру "что-то давно не было хартбита с той стороны") — я перевожу его состояние в состояние "сворачиваемся". После чего продолжения отработают и перезапущены не будут, и объект мирно умрет.
ЗЫ readed — такого слова нет. Read — неправильный глагол, у него все три формы совпадают.
Когда они сделают возможность закрыть сокет, и дождаться завершения всех асинхронных хэндлеров по нему без тонны костылей и/или без остановки io_service? Это же просто ужас какой-то!
Здравствуйте, о_О, Вы писали:
о_О>ну, ты эта, не массив сортируешь... о_О>асинхронные вещи требуют асинхронного управления. вся asio — костыль, но твой пример хреновый аргумент против.
С асинхронными вещами трудно работать. Иногда нужно просто выполнить несколько несложных операций, а они навязывают тебе целую идеологию и архитектуру.
Мне вот непонятно почему не предусмотреть простой механизм, который бы позволил дождаться завершения всех асинхронных операций по сокету. Это бы решило кучу проблем.
И вообще во всей документации и примерах по ASIO старательно умалчивают про корректное завершение работы.
У них там бесконечный цикл в main, а все данные обрабатываются прямо в классе клиента. А как только ты начинаешь взаимодействовать из клиент с остальными частями программы, опять возникает куча проблем.
Я не против того что так устроена сторонняя библиотека ASIO, но тащить непродуманное решение в стандарт я бы не стал.
Здравствуйте, smeeld, Вы писали:
S>Если нет требоавания кроссплатформа, то писать на нативном API ОС, будет проще, чем разбираться с S>фантазиями создателей asio.
Только в результате получится еще одно ASIO реализующее фантазии другого автора, только более глючное, так как в отладку вложить сопоставимые ресурсы будет трудно. Не нравится ASIO – возьми другую библиотеку по вкусу, благо альтернатив очень много.
Здравствуйте, niXman, Вы писали:
F>> MyClient *client = new MyClient();
Можно подумать, от auto client = std::make_shared<MyClient>() что-то изменится в понимании примера.
Implemented changes to substantially reflect the Networking Library Proposal (N4370).
New Executor type requirements and classes to support an executor framework, including the execution_context base class, the executor_work class for tracking outstanding work, and the executor polymorphic wrapper. Free functions dispatch(), post() and defer() have been added and are used to submit function objects to executors.
Completion handlers now have an associated executor and associated allocator. The free function wrap() is used to associate an executor with a handler or other object. The handler hooks for allocation, invocation and continuation have been deprecated.
A system_executor class has been added as a default executor.
The io_service class is now derived from execution_context and implements the executor type requirements in its nested executor_type class. The member functions dispatch(), post(), defer() and wrap() have been deprecated. The io_service::work class has been deprecated.
The io_service member function reset() has been renamed to restart(). The old name is retained for backward compatibility but has been deprecated.
The make_service<>() function is now used to add a new service to an execution context such as an io_service. The add_service() function has been deprecated.
A new strand<> template has been added to allow strand functionality to be used with generic executor types.
I/O objects (such as sockets and timers) now provide access to their associated io_service via a context() member function. The get_io_service() member function is deprecated.
All asynchronous operations and executor operations now support move-only handlers. However, the deprecated io_service::post(), io_service::dispatch(), io_service::strand::post() and io_service::strand::dispatch() functions still require copyable handlers.
Waitable timer objects are now movable.
Waitable timers, socket iostreams and socket streambufs now provide an expiry() member function for obtaining the expiry time. The accessors expires_at() and expires_after() have been deprecated, though those names are retained for the mutating members.
The std::packaged_task class template is now supported as a completion handler. The initiating operation automatically returns the future associated with the task. The package() function has been added as a convenient factory for packaged tasks.
Sockets, socket acceptors and descriptors now provide wait() and async_wait() operations that may be used to wait for readiness. The null_buffers type has been deprecated.
The proposed error code enum classes are simulated using namespaces. Existing asio error codes now have a correspondence with the standard error conditions.
Conversion between IP address types, and conversion from string to address, is now supported via the address_cast<>(), make_address(), make_address_v4() and make_address_v6() free functions. The from_string(), to_v4(), to_v6() and v4_mapped() member functions have been deprecated.
A default-constructed ip::address now represents an invalid address value that is neither IPv4 nor IPv6.
New buffer() overloads that generate mutable buffers for non-const string objects.
Support for dynamic buffer sequences that automatically grow and shrink to accomodate data as it is read or written. This is a generic facility similar to the existing asio::streambuf class. This support includes:
New dynamic_string_buffer and dynamic_vector_buffer adapter classes that meet the DynamicBufferSequence type requirements.
New dynamic_buffer() factory functions for creating a dynamic buffer adapter for a vector or string.
New overloads for the read(), async_read(), write() and async_write(), read_until() and async_read_until() free functions that directly support dynamic buffer sequences.
Support for networks and address ranges. Thanks go to Oliver Kowalke for contributing to the design and providing the implementation on which this facility is based. The following new classes have been added:
address_iterator_v4 for iterating across IPv4 addresses
address_iterator_v6 for iterating across IPv6 addresses
address_range_v4 to represent a range of IPv4 addresses
address_range_v6 to represent a range of IPv6 addresses
network_v4 for manipulating IPv4 CIDR addresses, e.g. 1.2.3.0/24
network_v6 for manipulating IPv6 CIDR addresses, e.g. ffe0:/120
New convenience headers in <asio/ts/*.hpp> that correspond to the headers in the proposal.
просто оставлю это тут.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Здравствуйте, fdn721, Вы писали:
F>А как? Ну вот к примеру стандартная ситуация:
[здесь был код] F>Как с этим бороться без кучи костылей???
Общепринятый подход показан в примерах к Boost.asio — положиться на подсчет ссылок в shared_ptr, передавать shared_ptr вместо this в асинхронные обработчики. Тогда при завершении последнего обработчика, объект будет удален.
class MyClient : public boost::enable_shared_from_this<MyClient>
{
protected:
tcp::socket m_socket;
public:
bool Open()
{
....
m_socket.async_connect( m_endpoint, boost::bind( &MyClient::HandleConnect, shared_from_this(), _1 ) );
}
void Close()
{
m_socket.close()
}
protected:
void HandleConnect( const boost::system::error_code& err )
{
//do somthing
}
}
int main()
{
...
boost::shared_ptr<MyClient> client = boost::make_shared<MyClient>();
client->Open();
client->Close(); //<<---- Тут ASIO запланирует вызов HandleConnect со значением error::operation_aborted.
// delete client; //<<---- не нужно, объект будет удален, когда на него не останется ссылок
...
}
Здравствуйте, PM, Вы писали:
PM>Общепринятый подход показан в примерах к Boost.asio — положиться на подсчет ссылок в shared_ptr, передавать shared_ptr вместо this в асинхронные обработчики. Тогда при завершении последнего обработчика, объект будет удален.
Да видел я все эти примеры, только они все однотипные. Вся обработка идёт внутри этого самого "MyClient". Только я не эхо сервер/клиент пишу. А как только начинаешь взаимодействовать из MyClient с остальной частью программы вылезают все те же самые проблемы с кучей асинхронных операций, которые невозможно просто взять и завершить.
ЗЫ По началу кажется что ASIO это очень мощное решение, а по факты всегда всё сводится к одному подходу. Шаг влево, шаг в право и у тебя куча проблем.
Здравствуйте, fdn721, Вы писали:
F>Да видел я все эти примеры, только они все однотипные. Вся обработка идёт внутри этого самого "MyClient". Только я не эхо сервер/клиент пишу. А как только начинаешь взаимодействовать из MyClient с остальной частью программы вылезают все те же самые проблемы с кучей асинхронных операций, которые невозможно просто взять и завершить.
F>ЗЫ По началу кажется что ASIO это очень мощное решение, а по факты всегда всё сводится к одному подходу. Шаг влево, шаг в право и у тебя куча проблем.
В вашем примере кода была обычная проблема со временем жизни объекта, которая решается обычным способом
Проблемы будут с asio, как и с какой-то другой библиотекой асинхронности, потому что асинхронность это сложно. И часто асинхронность используют еще и многопоточной среде, что только добавляет сложности.
Может быть я ошибаюсь, и есть альтернативы Asio? Я немного работал с libuv, там тоже не все просто.
Здравствуйте, fdn721, Вы писали:
F>Да видел я все эти примеры, только они все однотипные. Вся обработка идёт внутри этого самого "MyClient". Только я не эхо сервер/клиент пишу. А как только начинаешь взаимодействовать из MyClient с остальной частью программы вылезают все те же самые проблемы с кучей асинхронных операций, которые невозможно просто взять и завершить.
проверить в этой "куче асинхронных операций" error::operation_aborted религия не позволяет?
J>Что значит "handler ещё выполняется во время close"? Что один поток зовет close, пока в другом работает handler? Ну так защищать надо мьютексами, чтоб не было такого
Вот это я и называю костылями. Нужна насовать мютексов/флагов во все handler-ы только ради того, чтобы корректно закрыть соединение.
Здравствуйте, fdn721, Вы писали:
J>>Что значит "handler ещё выполняется во время close"? Что один поток зовет close, пока в другом работает handler? Ну так защищать надо мьютексами, чтоб не было такого
F>Вот это я и называю костылями. Нужна насовать мютексов/флагов во все handler-ы только ради того, чтобы корректно закрыть соединение.
Эти "костыли" нужны всегда, когда у тебя объекты шарятся между потоками, и asio тут вообще ни при чем.
Здравствуйте, PM, Вы писали:
PM>Вы только что описали ручное управление жизнью объектов, для которого и предназначен shared_ptr. И про удаление в случае ошибки нужно не забыть в каждом асинхронном обработчике
Где только таких учат и откуда они вообще берутся? А голову они нигде не забывают? Этот хронические
жабисты пусть забывают, любой C++ ник должен уметь вручную управлять объектами, несмотря на обилие
ptr-ов, ибо пригодится, и вообще, ручное управление памятью есть одно из ключевых возможностей в C++,
доставшихся в наследство из Cи.
PM>Почему-то вы исходите из предпослыки, что обработчики asio выполняются в одном потоке. На самом деле можно (и нужно в Windows) запускать несколько потоков обработки на одном io_service. В этом случае, в вашем примере кода в serv::start() потребуется mutex, защищающий conn_mas, как и реализации функции del_conn()
А можно вообще без mutex-ов, и вообще без каких либо локов, просто распареллелив данные между потоками.
Здравствуйте, PM, Вы писали:
PM>int main() PM>{ PM> ... PM> boost::shared_ptr<MyClient> client = boost::make_shared<MyClient>(); PM> client->Open(); PM> client->Close(); //<<---- Тут ASIO запланирует вызов HandleConnect со значением error::operation_aborted. PM>// delete client; //<<---- не нужно, объект будет удален, когда на него не останется ссылок PM> ... PM>}
Так тут точно такая же проблема: client будет удалён также после выхода из блока, что не гарантирует что все хендлеры завершаться до него.
Вообще сам подсчёт ссылок не решает проблем с удалением данных после завершения выполнения кода связанного с этими данными. А иногда делает даже хуже, т.к. ты не знаешь в какой момент объект разрушиться, нет вообще гарантий, что при написании какого-то кусочка кода объект в этот момент не будет жить или наоборот будет разрушен. Каждый раз при добавлении подобного кода приходится проходить мысленно по всей модели взаимодействий кода и данных. А это задалбывает.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, niXman, Вы писали:
X>Здравствуйте, flаt, Вы писали:
F>>Можно подумать, от auto client = std::make_shared<MyClient>() что-то изменится в понимании примера. X>ну, значит автору того хэлоуворда есть еще куда учиться бессмысленности... X>надеюсь, в следующий раз, в хэлоуворд, он обязательно впишет ваш, бессмысленный для хэлоуворда, пример
У тебя уже два абсолютно бессмысленных поста. Не надоело сотрясать воздух? Ест что написать по темы? Нет? Давай, до свиданья!
Здравствуйте, niXman, Вы писали:
X>ага. все, кто с тобой не согласны, или говорят то, что тебе не удобно — ведьмы и еретики, и их всех нужно сжечь. можешь сколько угодно выдавать желаемое за действительное, и сколь угодно лгать себе, но повторюсь: если ты ТАКОЕ пишешь в хэлоуворде — твои проблемы не в asio, от слова совсем.
1) Что такое хэлоуворд знаешь? Что-то я сомневаюсь. Это не хэлоуворд. Это пример, который я на коленке написал за 30 секунд.
2) У меня всего один вопрос, как закрыть сокет и дождаться освобождения всех ресурсов самым простым способом?
Что-то ни кто на него не так и не ответил. Ты похоже тоже не можешь на него ответить.
3) С кем я не согласился? Я всего лишь указал ряд проблем в том или ином подходе.
И наконец прилетаешь ты, и начинаешь мне рассказывать про опасность простых указателей, и про то что я ни чего не понимаю. Гениально!
Здравствуйте, fdn721, Вы писали:
F>Да зачем оно там нужно в таком виде???
F>Когда они сделают возможность закрыть сокет, и дождаться завершения всех асинхронных хэндлеров по нему без тонны костылей и/или без остановки io_service? Это же просто ужас какой-то!
Здравствуйте, niXman, Вы писали:
X>Implemented changes to substantially reflect the Networking Library Proposal (N4370).
Boost.Asio была предложена ещё в 2006 году в рамках TR2.
Здравствуйте, fdn721, Вы писали:
F>С асинхронными вещами трудно работать. Иногда нужно просто выполнить несколько несложных операций, а они навязывают тебе целую идеологию и архитектуру.
Именно поэтому сейчас продвигают resumable functions (async/await) как помощь в решении проблем с асинхронностью.
Здравствуйте, fdn721, Вы писали:
F>А как? Ну вот к примеру стандартная ситуация: F>Как с этим бороться без кучи костылей???
Кстати, можно использовать синхронные варианты
Здравствуйте, fdn721, Вы писали:
F>Здравствуйте, jazzer, Вы писали:
J>>проверить в этой "куче асинхронных операций" error::operation_aborted религия не позволяет?
F>Все такие умные, а если handler ещё выполняется во время close? Close ни чего не ждёт.
Что значит "handler ещё выполняется во время close"? Что один поток зовет close, пока в другом работает handler? Ну так защищать надо мьютексами, чтоб не было такого
Здравствуйте, kaa.python, Вы писали:
KP>Только в результате получится еще одно ASIO реализующее фантазии другого автора, только более глючное, так как в отладку вложить сопоставимые ресурсы будет трудно. Не нравится ASIO – возьми другую библиотеку по вкусу, благо альтернатив очень много.
Во первых, asio не глючное, во вторых, не факт что глючным будет своя реализация, и в третьих,
в своей реализации будут не отвлечённые фантазии, а воплощение именно того, что нужно в конкретном
приложении и ничего лишнего.
Здравствуйте, jazzer, Вы писали:
J>Эти "костыли" нужны всегда, когда у тебя объекты шарятся между потоками, и asio тут вообще ни при чем.
претензия fdn721 состоит в том, что asio не предлагает средств для уменьшения сложности написания программ с асинхр вводом\выводом (более конкретно, проблема завершения приложения и отмены асинхр операций, отслеживание времени жизни объектов). как следствие имеем хрупкий код, нестабильность, падения
именно asio должен был что-то предложить.
как вариант, сигнатура функции асинхронных операций должна не только принимать континуацию, но и выдавать хендл для управления запущенной операцией. это практикуется в том же C#: https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx https://msdn.microsoft.com/en-us/library/hh191443(v=vs.110).aspx
Здравствуйте, uzhas, Вы писали:
J>>Эти "костыли" нужны всегда, когда у тебя объекты шарятся между потоками, и asio тут вообще ни при чем. U>претензия ТС состоит в том, что asio не предлагает средств для уменьшения сложности написания программ с асинхр вводом\выводом
Здравствуйте, PM, Вы писали:
PM>Здравствуйте, fdn721, Вы писали:
F>>А как? Ну вот к примеру стандартная ситуация: PM>[здесь был код] F>>Как с этим бороться без кучи костылей???
PM>Общепринятый подход показан в примерах к Boost.asio — положиться на подсчет ссылок в shared_ptr, передавать shared_ptr вместо this в асинхронные обработчики. Тогда при завершении последнего обработчика, объект будет удален.
а как в Read при таком подходе избавиться от зацикливания ?
( я в принципе знаю — но это тоже костыль )
во вторых — например на таймерах можно дождаться их завершения — не понимаю какая проблема сделать тоже самое на сокетах ... можно конечно свой condition воткнуть, но почему бы
не делать это силами библиотеки?
Здравствуйте, The Passenger, Вы писали:
PM>>Общепринятый подход показан в примерах к Boost.asio — положиться на подсчет ссылок в shared_ptr, передавать shared_ptr вместо this в асинхронные обработчики. Тогда при завершении последнего обработчика, объект будет удален.
Это не подход, а оторванные от жизни примеры. Объект(будем называть его соединение) само по себе не живет(если это не эхо сервер), оно всё равно связано с остальной программой. Так вот реализовать эту связь с соединением, у которого какое-то произвольное время жизни, проблематично. Кроме того этим соединением часто приходится управлять из вне(т.е. им кто-то владеет, посылает данные, получает ответы). Ну вот к примеру надо переустановить соединение, как это сделать корректно при таком подходе? Всё обрастёт кучей мютексов и week_ptr-ов. Если у тебя есть хорошее решение — поделись, я тебе буду благодарен.
Здравствуйте, fdn721, Вы писали:
F>Это не подход, а оторванные от жизни примеры. Объект(будем называть его соединение) само по себе не живет(если это не эхо сервер), оно всё равно связано с остальной программой. Так вот реализовать эту связь с соединением, у которого какое-то произвольное время жизни, проблематично. Кроме того этим соединением часто приходится управлять из вне(т.е. им кто-то владеет, посылает данные, получает ответы). Ну вот к примеру надо переустановить соединение, как это сделать корректно при таком подходе? Всё обрастёт кучей мютексов и week_ptr-ов.
Для каждого соединения создаётся объект с отдельным asio сокетом как полем объекта. Объект,
при создании, помещается в дерево, список или хеш. Асинхронные обработчики будут являться методами
этих объектов. Этот объект уничтожается если в асинхронный хендлер передаётся сообщение об ошибке,
или закрытии соединения, после чего объект сам себя уничтожает, в своём же методе, то есть в асинхронном
обработчике. Всё операции и хендлеры обработки результатов их выполнения, исполняются в одном потоке io_service.
Поток io_service сам выбирает asio сокет для которого нужно вызвать хендлер, а тот уже определяет "свой" объект,
так как в asio функции хендлер "передаётся с объектом"
и совершает действия с его полями, составляя состояние того или иного соединения.
При уничтожении всех соединений, можно пройтись по всему дереву/списку с его очищением. Если одного потока io_service
оказыватся мало, можно создать их любое количество, с отдельным деревом соединений на каждый.
S>Для каждого соединения создаётся объект с отдельным asio сокетом как полем объекта. Объект, S>при создании, помещается в дерево, список или хеш. Асинхронные обработчики будут являться методами S>этих объектов. Этот объект уничтожается если в асинхронный хендлер передаётся сообщение об ошибке, S>или закрытии соединения, после чего объект сам себя уничтожает, в своём же методе, то есть в асинхронном S>обработчике. Всё операции и хендлеры обработки результатов их выполнения, исполняются в одном потоке io_service. S>Поток io_service сам выбирает asio сокет для которого нужно вызвать хендлер, а тот уже определяет "свой" объект, S>так как в asio функции хендлер "передаётся с объектом"
Вы только что описали ручное управление жизнью объектов, для которого и предназначен shared_ptr. И про удаление в случае ошибки нужно не забыть в каждом асинхронном обработчике
Почему-то вы исходите из предпослыки, что обработчики asio выполняются в одном потоке. На самом деле можно (и нужно в Windows) запускать несколько потоков обработки на одном io_service. В этом случае, в вашем примере кода в serv::start() потребуется mutex, защищающий conn_mas, как и реализации функции del_conn()
Ну то есть в итоге придем к тому, что fdn721 назвал костылями.
Здравствуйте, The Passenger, Вы писали:
PM>>Общепринятый подход показан в примерах к Boost.asio — положиться на подсчет ссылок в shared_ptr, передавать shared_ptr вместо this в асинхронные обработчики. Тогда при завершении последнего обработчика, объект будет удален.
TP>а как в Read при таком подходе избавиться от зацикливания ? TP>( я в принципе знаю — но это тоже костыль )
Зацикливания чего, ссылок на объекты или обработчиков? Непонятный вопрос.
TP>во вторых — например на таймерах можно дождаться их завершения — не понимаю какая проблема сделать тоже самое на сокетах ... можно конечно свой condition воткнуть, но почему бы TP>не делать это силами библиотеки?
Я не автор, но могу предположить, потому что это непросто сделать кроссплатформенно.
Здравствуйте, fdn721, Вы писали:
F>Это не подход, а оторванные от жизни примеры. Объект(будем называть его соединение) само по себе не живет(если это не эхо сервер), оно всё равно связано с остальной программой. Так вот реализовать эту связь с соединением, у которого какое-то произвольное время жизни, проблематично.
Вот у меня, например, совсем не эхо сервер. И соединение прекрасно живёт себе в виде умного указателя в недрах io_service + у меня в списке. После того, как я для соединения делаю close и удаляю из своего списка соединений, как только asio завершит для него все асинхронные операции, связанные с закрытием сокета, оно прекрасно удаляется из недр io_service, после чего умный указатель вызывает деструктор объекта соединения. Это замечательно работает, избавляя меня от необходимости ручного управления памятью.
Здравствуйте, smeeld, Вы писали:
PM>>Вы только что описали ручное управление жизнью объектов, для которого и предназначен shared_ptr. И про удаление в случае ошибки нужно не забыть в каждом асинхронном обработчике
S>Где только таких учат и откуда они вообще берутся? А голову они нигде не забывают? Этот хронические S>жабисты пусть забывают, любой C++ ник должен уметь вручную управлять объектами, несмотря на обилие S>ptr-ов, ибо пригодится, и вообще, ручное управление памятью есть одно из ключевых возможностей в C++, S>доставшихся в наследство из Cи.
Жизнь таких учит, что в С++ коде внезапно бывают исключения, что люди ошибаются, что код меняется. Должен уметь != должен делать. Если вам незнакомо RAII и нравится писать в стиле С, так и пишите на С, пользуйтесь на здоровье goto cleanup.
PM>>Почему-то вы исходите из предпослыки, что обработчики asio выполняются в одном потоке. На самом деле можно (и нужно в Windows) запускать несколько потоков обработки на одном io_service. В этом случае, в вашем примере кода в serv::start() потребуется mutex, защищающий conn_mas, как и реализации функции del_conn()
S>А можно вообще без mutex-ов, и вообще без каких либо локов, просто распареллелив данные между потоками.
Можно многое, но если будет использоваться пул потоков для обработчиков асинхронных операций, без блокировок в каком-либо виде не обойтись.
Хотя, дискутировать с вами не собираюсь, тема была не про это. Ваша позиция ясна из предыдущих сообщений. Надеюсь, мне не придется с вами работать в одной команде, либо поддерживать ваш код.
Здравствуйте, fdn721, Вы писали:
F>Да видел я все эти примеры, только они все однотипные. Вся обработка идёт внутри этого самого "MyClient". Только я не эхо сервер/клиент пишу. А как только начинаешь взаимодействовать из MyClient с остальной частью программы вылезают все те же самые проблемы с кучей асинхронных операций, которые невозможно просто взять и завершить.
F>ЗЫ По началу кажется что ASIO это очень мощное решение, а по факты всегда всё сводится к одному подходу. Шаг влево, шаг в право и у тебя куча проблем.
Несколько лет назад смотрел код libtorrent, там использовалась asio.
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, jazzer, Вы писали:
J>>Эти "костыли" нужны всегда, когда у тебя объекты шарятся между потоками, и asio тут вообще ни при чем.
U>претензия fdn721 состоит в том, что asio не предлагает средств для уменьшения сложности написания программ с асинхр вводом\выводом (более конкретно, проблема завершения приложения и отмены асинхр операций, отслеживание времени жизни объектов). как следствие имеем хрупкий код, нестабильность, падения U>именно asio должен был что-то предложить. U>как вариант, сигнатура функции асинхронных операций должна не только принимать континуацию, но и выдавать хендл для управления запущенной операцией.
Ну так тогда придется где-то хранить эти хэндлы, так что тот же гемор, вид сбоку
У fdn721 же добавление континуации и удаление сокета разнесены в коде, я так предполагаю (иначе можно было бы просто континуацию не создавать, если мы знаем, что тут же сокет прибьем)
Далее, зачем обязательно убивать сокет явно? Достаточно хранить его в любом умном указателе и передавать явно в континуацию. Тогда при закрытии сокета в континуацию придет operation_aborted — ты в результате просто не поставишь в очередь очередную континуацию (которая держала бы сокет на плаву), и сокет мирно помрет сам вместе с указателем.
ЗЫ Есть какой-нть нормальный перевод для continuation? Калька — это уродство какое-то "Продолжение" тоже плохо выглядит, так как имеет самостоятельный смысл. "Функция/процедура продолжения"?
Здравствуйте, jazzer, Вы писали:
J>Ну так тогда придется где-то хранить эти хэндлы, так что тот же гемор, вид сбоку
это не вид сбоку, это доп. рычаг, который позволяет в другой манере работать с теми же вещами. и может гораздо проще решить проблему fdn721
это все равно, что отождествить все языки программирования "они же все эквивалентны машине Тьюринга". хотя разные языки одну и ту же задачу могут решать по-разному в плане перформанса, простоты кода\сопровождения и тд.
J>Далее, зачем обязательно убивать сокет явно? Достаточно хранить его в любом умном указателе и передавать явно в континуацию. Тогда при закрытии сокета в континуацию придет operation_aborted — ты в результате просто не поставишь в очередь очередную континуацию (которая держала бы сокет на плаву), и сокет мирно помрет сам вместе с указателем.
работа с внешними ресурсами часто требует полного и явного контроля. если говорить о сокетах, то иногда нужно явно освободить порт. полагаться на shared_ptr я бы не стал из-за непредсказуемости кол-ва владельцев (более точно из-за непредсказуемости момента уничтожения захваченного объекта)
J>ЗЫ Есть какой-нть нормальный перевод для continuation? Калька — это уродство какое-то "Продолжение" тоже плохо выглядит, так как имеет самостоятельный смысл. "Функция/процедура продолжения"?
обычно переводят, как "продолжение".
у слова "функция" и "процедура" тоже много значений в русском языке, главное — привыкнуть
но в данном контексте все же это слово не следует употреблять, у него несколько другое значение : http://en.wikipedia.org/wiki/Continuation
в данном случае мы все же работаем с async method handler, то есть с обработчиками асинхронных операций, которые в свою очередь часто являются замыканиями ( http://en.wikipedia.org/wiki/Closure_(computer_programming) ) имхо
U>а пример вот о чем: U>1) создается объект U>2) запускается асинхр операция по коннекту (или чтению данных) U>3) уничтожается объект явным образом
При использовании stackless coroutines так же как и в обычном случае — при входе в автомат делается проверка error_code. И если там ошибка — то передача продолжения в следующую асинхронную операцию не происходит, текущее продолжение тихо завершается и либо самоудаляется, либо уменьшает ref count.
В случае stackful — при ошибке происходит либо выброс исключения (стэк раскручивается и корутина завершается), либо же можно получить тот же error_code и самому решить что делать дальше.
Оба примера есть в Asio (1, 2), там разве что не хватает вызова close.
Здравствуйте, uzhas, Вы писали:
J>>Ну так тогда придется где-то хранить эти хэндлы, так что тот же гемор, вид сбоку U>это не вид сбоку, это доп. рычаг, который позволяет в другой манере работать с теми же вещами. и может гораздо проще решить проблему fdn721 U>это все равно, что отождествить все языки программирования "они же все эквивалентны машине Тьюринга". хотя разные языки одну и ту же задачу могут решать по-разному в плане перформанса, простоты кода\сопровождения и тд.
Покажи как бы выглядел код использующий Asio с этими хэндлерами.
J>>Далее, зачем обязательно убивать сокет явно? Достаточно хранить его в любом умном указателе и передавать явно в континуацию. Тогда при закрытии сокета в континуацию придет operation_aborted — ты в результате просто не поставишь в очередь очередную континуацию (которая держала бы сокет на плаву), и сокет мирно помрет сам вместе с указателем. U>работа с внешними ресурсами часто требует полного и явного контроля.
Часто нужен не явный контроль, а всего лишь prompt finalization, который прекрасно обеспечивается RAII.
U>если говорить о сокетах, то иногда нужно явно освободить порт. полагаться на shared_ptr я бы не стал из-за непредсказуемости кол-ва владельцев
Не надо раздавать shared_ptr кому попало.
U>(более точно из-за непредсказуемости момента уничтожения захваченного объекта)
Если в какой-то точке программы (в другом потоке?) нужно дождаться момента разрушения какого-то объекта, то необходимо делать блокирующее ожидание.
U>обычно переводят, как "продолжение". U>у слова "функция" и "процедура" тоже много значений в русском языке, главное — привыкнуть U>но в данном контексте все же это слово не следует употреблять, у него несколько другое значение : http://en.wikipedia.org/wiki/Continuation
Ты ссылаешься на то continuation, которое подразумевается в call-with-current-continuation. Есть же второе значение, которое в подразумевается в continuation-passing style.
Тем не менее эти значения очень близки. Я бы даже сказал что это не разные значения continuation, а само понятие continuation более общее, которое и покрывает эти оба конкретных случая — CPS или call/cc.
U>в данном случае мы все же работаем с async method handler, то есть с обработчиками асинхронных операций, которые в свою очередь часто являются замыканиями ( http://en.wikipedia.org/wiki/Closure_(computer_programming) ) имхо
Продолжения тоже часто являются замыканиями.
Я считаю что вполне уместно называть хэндлер асихнронной операции продолжением — это уже устоявшаяся практика.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Покажи как бы выглядел код использующий Asio с этими хэндлерами.
например, так:
class MyClient
{
protected:
tcp::socket m_socket;
task<void> m_ConnectTask;
public:
bool Open()
{
....
m_ConnectTask = m_socket.async_connect( m_endpoint, boost::bind( &MyClient::HandleConnect, this, _1 ) );
}
void Close()
{
m_socket.close();
m_ConnectTask.wait(); //blocks if there is incomplete async op, non blocking if there is no associated async op
}
protected:
void HandleConnect( const boost::system::error_code& err )
{
//do something
}
}
int main()
{
...
MyClient *client = new MyClient();
client->Open();
client->Close();
delete client;
...
}
EP>Часто нужен не явный контроль, а всего лишь prompt finalization, который прекрасно обеспечивается RAII.
я не вижу, как RAII поможет в примере выше. более того, socket.close — это не RAII, однако без него было бы еще сложнее =)
EP>Не надо раздавать shared_ptr кому попало.
дисциплину тяжело крыть автотестами, нужны более понятные средства, помогающие писать надежный софт
EP>Если в какой-то точке программы (в другом потоке?) нужно дождаться момента разрушения какого-то объекта, то необходимо делать блокирующее ожидание.
EP>Ты ссылаешься на то continuation, которое подразумевается в call-with-current-continuation. Есть же второе значение, которое в подразумевается в continuation-passing style.
в CPS тоже речь не о колбеке, а о целой ветке исполнения
EP>Продолжения тоже часто являются замыканиями.
согласен
EP>Я считаю что вполне уместно называть хэндлер асихнронной операции продолжением — это уже устоявшаяся практика.
а я считаю наоборот, это плохая практика, также как и писать "длинна", хоть это и распространенная практика
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, uzhas, Вы писали:
U>>полагаться на shared_ptr я бы не стал из-за непредсказуемости кол-ва владельцев (более точно из-за непредсказуемости момента уничтожения захваченного объекта)
J>порт уже будет освобожден при помощи socket.close(). shared_ptr просто удалит неактивный объект, когда asio дернет все продолжения, которые с ним связаны (c operation_aborted).
Всё маленько сложнее, хэндлеры(продолжения) могут в момент socket.close() исполняться. Если после socket.close() сразу разрывать связь MyClient с остальной программой (освободить m_dataProcessor), то мы нарвёмся на очень нехорошую ситуацию.
Можно использовать week_ptr<IDataProcessor>, но это спасёт только от нулевого указателя. Мы всё равно можем получить вызов m_dataProcessor->ProcessData(...) когда считаем что соединение уже завершено. Это неприятный побочный эффект, который надо учитывать в IDataProcessor.
Можно в каждый хэндлер и Close добавить приметив синхронизации, и проверять состояние сокета. Но это костыль.
Можно руками следить за текущим состоянием всех хэндлеров и ждать в Close их завершения, и это тоже костыль.
PS В общем после попытки использовать asio осталось стойкое впечатление что я что-то делаю не так. Но ни где не написано как надо делать. Всё больше советов от теоретиков, типа используй shared_from_this и всё получится.
F>Можно использовать week_ptr<IDataProcessor>, но это спасёт только от нулевого указателя. Мы всё равно можем получить вызов m_dataProcessor->ProcessData(...) когда считаем что соединение уже завершено. Это неприятный побочный эффект, который надо учитывать в IDataProcessor.
F>Можно в каждый хэндлер и Close добавить приметив синхронизации, и проверять состояние сокета. Но это костыль.
F>Можно руками следить за текущим состоянием всех хэндлеров и ждать в Close их завершения, и это тоже костыль.
F>PS В общем после попытки использовать asio осталось стойкое впечатление что я что-то делаю не так. Но ни где не написано как надо делать. Всё больше советов от теоретиков, типа используй shared_from_this и всё получится.
Как уже написал jazzer, обычно в активных объектах такого типа можно использовать некий конечный автомат, так что в состоянии `closed connection` обработка данных не будет выполняться.
Но, в принципе, волшебства не будет — в многопоточной программе данные придется защищать. Как вариант, можно поставить на исполнение функцию закрытия сокета в очередь к другим обработчикам с помощью `io_service::post([](){ m_socket.close(); })`
Еще желательно помнить, что если асинхронные обработчики выполняются в пуле потоков, то для соблюдения последовательности вызовов обработчиков нужно использовать io_service::strand.
Подход с coroutines, упомянутый Evgeny.Panasyuk выглядит проще, но это тоже нужно разбираться и читать документацию. Я на практике еще не использовал coroutines.
Здравствуйте, Vain, Вы писали:
PM>>int main() PM>>{ PM>> ... PM>> boost::shared_ptr<MyClient> client = boost::make_shared<MyClient>(); PM>> client->Open(); PM>> client->Close(); //<<---- Тут ASIO запланирует вызов HandleConnect со значением error::operation_aborted. PM>>// delete client; //<<---- не нужно, объект будет удален, когда на него не останется ссылок PM>> ... PM>>} V>Так тут точно такая же проблема: client будет удалён также после выхода из блока, что не гарантирует что все хендлеры завершаться до него.
Вообще то этот пример код не является минимально полным рабочим, ни у fdn721, ни у меня. Обычно таки в программе есть что-то типа io_thread.join() которое гарантирует завершение обработчиков asio до выхода из какого-то блока.
V>Вообще сам подсчёт ссылок не решает проблем с удалением данных после завершения выполнения кода связанного с этими данными. А иногда делает даже хуже, т.к. ты не знаешь в какой момент объект разрушиться, нет вообще гарантий, что при написании какого-то кусочка кода объект в этот момент не будет жить или наоборот будет разрушен. Каждый раз при добавлении подобного кода приходится проходить мысленно по всей модели взаимодействий кода и данных. А это задалбывает.
Серебряной пули нет Я вообще не часто использую shared_ptr, но для откладывания на потом решения, когда удалить объект он полезен. Мысленно ходить далеко не надо — объект не разрушится, пока на него есть ссылки. Для слабых ссылок есть weak_ptr, для которого проверяется результат lock()
Проверяли же 15 лет назад сырые указатели на NULL и не жаловались. А некоторые до сих пор агитируют за ручное управление памятью.
Здравствуйте, uzhas, Вы писали:
EP>>Покажи как бы выглядел код использующий Asio с этими хэндлерами. U>например, так: U>
U> m_socket.close();
U> m_ConnectTask.wait(); //blocks if there is incomplete async op, non blocking if there is no associated async op
U>
А каков механизм блокирования? Там могут быть разные случаи:
1. Всё происходит в одном потоке, в котором запущен io_service.
Заблокировать поток нельзя, так как тогда не выполнится хэндлер — получаем deadlock. Нужно внутри wait'а крутить io_service, например через run_one. Но, если соединений очень много, и каждое будет делать такой wait, можем запросто получить stackoverflow — конечно можем увеличить адресное пространство стэка, но как-то получается не айс.
2. В одном потоке запущен io_service, а ждать нужно в другом, в котором он не запущен. Здесь достаточно future.
3. Есть два потока, в каждом из которых крутится свой io_service, либо один и тот же. Полностью блокировать плохо, нужно крутить run_one — что опять же может привести к stackoverflow.
EP>>Часто нужен не явный контроль, а всего лишь prompt finalization, который прекрасно обеспечивается RAII. U>я не вижу, как RAII поможет в примере выше. более того, socket.close — это не RAII, однако без него было бы еще сложнее =)
socket.close это действительно не RAII. RAII помогает освободить ресурсы в этих хэндлерах после этого явного close, причём как prompt finalization.
EP>>Не надо раздавать shared_ptr кому попало. U>дисциплину тяжело крыть автотестами,
Можно ограничивать доступ через private.
U>нужны более понятные средства, помогающие писать надежный софт
Проблема с ресурсами в контексте асинхронных обработчиков есть и в GC языках — там это один из основных use-case'ов для утечек, только помимо этого ещё и отсутствует нормальное prompt finalization.
EP>>Если в какой-то точке программы (в другом потоке?) нужно дождаться момента разрушения какого-то объекта, то необходимо делать блокирующее ожидание. EP>>Ты ссылаешься на то continuation, которое подразумевается в call-with-current-continuation. Есть же второе значение, которое в подразумевается в continuation-passing style. U>в CPS тоже речь не о колбеке, а о целой ветке исполнения
Так при использовании Asio тоже приходится делать целую ветку исполнения.
EP>>Я считаю что вполне уместно называть хэндлер асихнронной операции продолжением — это уже устоявшаяся практика. U>а я считаю наоборот, это плохая практика, также как и писать "длинна", хоть это и распространенная практика
У тебя может есть ссылка на значимый первоисточник, в котором чётко написано что такое handler, а что такое continuation?
Если такого каноничного источника нет, то и приходится опираться на распространенную практику
ага. все, кто с тобой не согласны, или говорят то, что тебе не удобно — ведьмы и еретики, и их всех нужно сжечь. можешь сколько угодно выдавать желаемое за действительное, и сколь угодно лгать себе, но повторюсь: если ты ТАКОЕ пишешь в хэлоуворде — твои проблемы не в asio, от слова совсем.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)