2014-06-30 19 views
13

Próbuję przerobić projekt przy użyciu coroutines boost::asio::spawn. Niektórych części projektu nie można zmienić. Na przykład biblioteka protokołów magazynowania jest również zapisana pod numerem boost::asio, ale bez coroutines.boost :: asio :: spawn yield jako callback

Problem polega na przekształceniu yield_context w zwykłe wywołanie zwrotne (obiekt boost::function lub klasyczny funktor).

To co mamy w API biblioteki Przechowywanie:

void async_request_data(uint64_t item_id, boost::function< void(Request_result *) > callback); 

Jak wiemy z przykładach asio kontekst wydajność mogą być używane tak:

my_socket.async_read_some(boost::asio::buffer(data), yield); 

W tym przypadku boost::asio::yield_context Obiekt służy jako wywołanie zwrotne dla async_read_some. Chciałbym przekazać obiekt yield jako drugi argument do async_request_data, więc mogę go używać w sposób synchroniczny.

Jak można tego dokonać? Myślę, że może to być możliwe za pośrednictwem jakiegoś obiektu proxy, prawdopodobnie za pomocą podejścia opartego na asio_handler_invoke. Ale mam problem z zobaczeniem, jak to zrobić.

Odpowiedz

12

Wygląda jak najlepszej dokumentacji dla tej funkcji można znaleźć w C++ standardowy wniosek napisany przez autora Boost ASIO:

N4045 – Library Foundations for Asynchronous Operations, Revision 2

Patrz rozdział 9.1, który mówi:

handler_type_t<CompletionToken, void(error_code, size_t)> #3 
    handler(std::forward<CompletionToken>(token)); 

3: The completion token is converted into a handler, i.e. a function object to be called when the asynchronous operation completes. The signature specifies the arguments that will be passed to the handler.

Domyślam się, że w twoim przypadku argument szablonu CompletionToken będzie w rzeczywistości boost::asio::yield_context i handler_type przekształca go w obiekt wywołania zwrotnego.


Oto kod z sekcji 9.1 zaktualizowane nazwać swoją funkcję async_request_data:

template <class CompletionToken> 
auto async_foo(uint64_t item_id, CompletionToken&& token) 
{ 
    handler_type_t<CompletionToken, void(Request_result *)> 
    handler(std::forward<CompletionToken>(token)); 

    async_result<decltype(handler)> result(handler); 

    async_request_data(item_id, handler); 

    return result.get(); 
} 
+0

Na pewno muszę jakiś obiekt proxy, aby przekazać go jako zwrotnego, ale nie jest jasne, w jaki sposób napisać wnętrzności tego obiektu. 'yield_context' nie ma' operator() '(inaczej działałoby" tak jak jest "bez proxy). Ma pewne odwagi opisane tutaj: http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/basic_yield_context/basic_yield_context.html, ale nie jest jasne, jak je połączyć, aby uzyskać poprawne CV Coroutine . – PSIAlt

+0

Myślę, że 'handler' ** jest ** obiektem proxy. Pls patrz edit. –

+0

To działało z niewielkimi zmianami! Dzięki za wskazówki! (Napisałem ostatni kod w innej odpowiedzi) – PSIAlt

3

Wielkie dzięki free_coffe udało mi to do pracy. Publikowanie rozwiązania dla mojej sprawy, być może ktoś tego potrzebuje.

template <class CompletionToken> 
RequestResult async_foo(Packet &pkt, CompletionToken&& token) { 
    typename boost::asio::handler_type< CompletionToken, void(RequestResult) >::type handler(std::forward<CompletionToken>(token)); 
    boost::asio::async_result<decltype(handler)> result(handler); 
    storage_api->writePacket(pkt, handler); 
    return result.get(); 
} 

Później możemy użyć tego proxy:

RequestResult res = async_foo(pkt, std::forward<boost::asio::yield_context>(yield)); 
+0

Świetnie! Nieprzyjemnie pachnie przy użyciu klasy 'detail'. Może możesz opublikować błędy od kiedy próbowałeś użyć 'handler_type'? –

+0

@ free_coffee 'error: could not convert 'result.boost :: asio :: async_result :: get >>() 'from' boost :: asio :: async_result >>: : type {aka void} 'to "RequestResult '' – PSIAlt

+0

Wygląda na to, że' yield_context' jest w jakiś sposób przekazywane jako parametr szablonu 'async_result', co oznacza, że' decltype (handler) 'nie jest tym, czym powinien być. Czy możesz napisać kod? –

6

Dzięki @PSIAlt i @free_coffee wiem, jak korzystać z funkcji zwrotnych w stackful współprogram.

Oto prosty przykład tylko dla ASIO początkujących (jak ja: D)

https://gist.github.com/chenfengyuan/4d764b0bca82a42c05a9

#include <iostream> 
#include <boost/asio.hpp> 
#include <boost/date_time/posix_time/posix_time.hpp> 
#include <boost/asio/spawn.hpp> 
#include <memory> 

void bar(boost::asio::io_service &io, std::function<void()> cb){ 
    auto ptr = std::make_shared<boost::asio::deadline_timer>(io, boost::posix_time::seconds(1)); 
    ptr->async_wait([ptr, cb](const boost::system::error_code&){cb();}); 
} 

template<typename Handler> 
void foo(boost::asio::io_service &io, Handler && handler){ 
    typename boost::asio::handler_type<Handler, void()>::type handler_(std::forward<Handler>(handler)); 
    boost::asio::async_result<decltype(handler_)> result(handler_); 
    bar(io, handler_); 
    result.get(); 
    return; 
} 

int main() 
{ 
    boost::asio::io_service io; 
    boost::asio::spawn(io, [&io](boost::asio::yield_context yield){ 
     foo(io, yield); 
     std::cout << "hello, world!\n"; 
    }); 

    io.run(); 

    return 0; 
} 
+4

Przyjemny minimalny przykład! Istnieje subtelny problem. 'handler_' musi zostać wywołany przez hak [' asio_handler_invoke() '] (http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/reference/asio_handler_invoke.html) w celu właściwej synchronizacji z coroutine . W przeciwnym razie, w środowisku wielowątkowym, może wystąpić sytuacja wyścigowa, w której próbuje się wznowić działanie korupcji przed jej uzyskaniem. Haczyk 'asio_handler_invoke' jest przeciążony dla określonych typów, więc nie można wydać polecenia typu" wymazanie do 'std :: function <>". [Tutaj] (http://coliru.stacked-crooked.com/a/c48d1dcf4608e967) jest zaktualizowanym rozwiązaniem. –

Powiązane problemy