2014-12-17 27 views
14

Piszę kod wielowątkowy i używając obietnicy/przyszłości wywołuję funkcję na innym wątku i zwracam jej wynik. Dla simplicitly, usunę gwintu udział w całości:Wypełnij obietnicę (prawdopodobnie nieważną)

template <typename F> 
auto blockingCall(F f) -> decltype(f()) 
{ 
    std::promise<decltype(f())> promise; 

    // fulfill the promise 
    promise.set_value(f()); 

    // block until we have a result 
    return promise.get_future().get(); 
} 

Działa to doskonale dla każdej funkcji, która zwraca nieprzestrzegania void. Oświadczenie zwrotu dotyczące przyszłości również działa dla void. Ale nie mogę spełnić obietnicę jeśli f jest void funkcja, ponieważ:

promise.set_value (f()); // błąd: nieprawidłowe użycie void ekspresji

Czy jest jakiś sprytny sposób ustalania wartości w przypadku in-line void, czy mam po prostu napisać funkcję pomocnika jak call_set_value(promise, f) który ma przeciążeń dla std::promise<R> i std::promise<void>?

+0

Nie mam umiejętności związanych z szablonem, aby to zrobić samodzielnie, ale mam wrażenie, że prawdopodobnie można SFINAE na typem zwrotu. – Borgleader

+2

Jaki jest dokładnie sens wywoływania funkcji w innym wątku, jeśli masz zamiar poczekać, aż połączenie zostanie wykonane? (Z wyjątkiem oczywistego przypadku wysłania wywołania do * określonego * wątku). – cdhowie

+4

Dlaczego korzystasz z obietnicy niskiego poziomu - możesz po prostu użyć std :: future –

Odpowiedz

11

promise jest tylko jeden rodzaj asynchronicznego dostawcy wynikowego. Zamiast promise można użyć packaged_task który otacza wywoływalnym przedmiot, podobny do std::function wyjątkiem tego, że powołując się na to sprawia, że ​​wynik jest dostępny za pośrednictwem future (i oczywiście obsługuje różnica między void i non-void wyniki):

template <typename F> 
auto blockingCall(F f) -> decltype(f()) 
{ 
    std::packaged_task<decltype(f())()> task(std::move(f)); 

    task(); 

    // block until we have a result 
    return task.get_future().get(); 
} 

NB zgodnie z aktualnym standardem, kod ten miałby wyścig danych, gdyby task() i task.get_future() stało się na osobnych wątkach (i tak samo twój oryginał użyłby obietnicy), więc powinieneś zadzwonić pod numer get_future(), zanim przekażesz zadanie drugiemu wątkowi. W praktyce powinno to być bezpieczne w przypadku rzeczywistych implementacji i istnieje kwestia biblioteki (LWG 2412), aby i tak była ważna.

+0

Dlaczego nie mogę przenieść zadania do 'std :: function'? na przykład 'std :: function func = [t = std :: move (task)]() mutable {t(); }; 'podaje błąd dotyczący' zadania' będącego niemożliwym do skopiowania. – Barry

+1

'std :: function' jest kopiowalna, więc wymaga również, aby obiekt docelowy był kopiowalny (typ celu jest kasowany, więc' funkcja' nie może być kopiowana tylko wtedy, gdy owija obiekt kopiowalny, ponieważ cel może zmieniać się dynamicznie w czasie wykonywania). 'packaged_task' jest tylko w ruchu, więc nie można go zapisać w' funkcji' –

+0

Och, prawda! Chłodny. Podoba mi się to rozwiązanie. Cieszę się też, że zmieniłeś swój kapelusz z Spocka, bo to zdecydowanie sprawiło, że twoje zdjęcie z jednym okiem było jeszcze bardziej nieprzyjemne :) – Barry

12

Tak. przeciążenie funkcja jest najczystszym rozwiązanie:

set(promise, f); 

następnie wdrożyć set jako przeciążonych funkcji, jak:

template<typename F, typename R> 
void set(std::promise<R> & p, F && f) //handle non-void here 
{ 
    p.set_value(f()); 
} 

template<typename F> 
void set(std::promise<void> & p, F && f) //handle void here 
{ 
    f(); 
    p.set_value(); 
}