Co to jest std:: promise?
Jestem dość zaznajomiony z C++11 ' S std::thread
, std::async
i std::future
składniki (np. patrz ta ODPOWIEDŹ ), które są proste.
Nie mogę jednak do końca zrozumieć, czym jest std::promise
, co robi i w jakich sytuacjach najlepiej go używać. Sam standardowy dokument nie zawiera wielu informacji poza jego klasą synopsis, podobnie jak po prostu:: thread .
Czy ktoś mógłby podać krótki, zwięzły przykład sytuacji, w której potrzebny jest std::promise
i gdzie jest najbardziej idiomatyczne rozwiązanie?
8 answers
W słowach [futures.stan] a std::future
jest asynchronicznym obiektem return ("obiekt, który odczytuje wyniki ze stanu współdzielonego") i a std::promise
jest asynchronicznym dostawcą ("obiekt, który dostarcza wynik do stanu współdzielonego") tzn. obietnica jest rzeczą, którą ustawiasz wynik, aby uzyskać to z powiązanej przyszłości.
Asynchroniczny dostawca jest tym, co początkowo tworzy wspólny stan, do którego odnosi się przyszłość. std::promise
jest jeden typ dostawcy asynchronicznego, std::packaged_task
Jest Inny, a wewnętrzny detal {[4] } jest inny. Każdy z nich może utworzyć stan współdzielony i dać std::future
, który dzieli ten stan i może go przygotować.
std::async
jest wygodnym narzędziem wyższego poziomu, które zapewnia asynchroniczny obiekt wynikowy i wewnętrznie zajmuje się tworzeniem asynchronicznego dostawcy i przygotowaniem współdzielonego stanu po zakończeniu zadania. Można go emulować za pomocą std::packaged_task
(lub std::bind
i std::promise
) i std::thread
, ale jest bezpieczniejszy i łatwiejszy w użyciu std::async
.
std::promise
jest nieco niższym poziomem, ponieważ gdy chcesz przekazać asynchroniczny wynik do przyszłości, ale kod, który sprawia, że wynik jest gotowy, nie może być zawinięty w jedną funkcję odpowiednią do przekazania do std::async
. Na przykład, możesz mieć tablicę kilku promise
s i powiązanych future
S i mieć pojedynczy wątek, który wykonuje kilka obliczeń i ustawia wynik dla każdej obietnicy. async
umożliwi zwrócenie tylko jednego wyniku, aby zwrócić kilka, musisz zadzwonić async
kilka razy, co może zmarnować zasoby.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-03 10:19:45
Rozumiem teraz nieco lepiej sytuację (w niedużej ilości ze względu na odpowiedzi tutaj!), więc pomyślałem, że dodam trochę własnego zapisu.
W C++11 istnieją dwa różne, choć powiązane pojęcia: asynchroniczne obliczenia( funkcja, która jest wywoływana gdzie indziej) i wykonywanie współbieżne (wątek {47]} {48]}, coś, co działa jednocześnie). Te dwa pojęcia są nieco ortogonalne. Obliczenia asynchroniczne to tylko inny smak wywołania funkcji, podczas gdy wątek jest kontekstem wykonania. Wątki są przydatne same w sobie, ale na potrzeby tej dyskusji potraktuję je jako szczegół implementacji.
Istnieje hierarchia abstrakcji dla obliczeń asynchronicznych. Na przykład, załóżmy, że mamy funkcję, która pobiera pewne argumenty:
int foo(double, char, bool);
Po pierwsze, mamy szablon std::future<T>
, która reprezentuje przyszłą wartość typu T
. Wartość można pobrać poprzez funkcja member get()
, która skutecznie synchronizuje program oczekując na wynik. Alternatywnie, future obsługuje wait_for()
, który może być użyty do sprawdzenia, czy wynik jest już dostępny. Kontrakty Futures należy traktować jako asynchroniczny zamiennik drop-in dla zwykłych typów zwrotu. Dla przykładowej funkcji oczekujemy std::future<int>
.
Teraz, do hierarchii, od najwyższego do najniższego poziomu:
-
std::async
: najwygodniejszy i prosta droga do wykonania obliczeń asynchronicznych jest za pomocą szablonu funkcjiasync
, który natychmiast zwraca dopasowaną przyszłość:
Mamy bardzo małą kontrolę nad szczegółami. W szczególności, nie wiemy nawet, czy funkcja jest wykonywana równolegle, szeregowo naauto fut = std::async(foo, 1.5, 'x', false); // is a std::future<int>
get()
, czy też przez jakąś inną czarną magię. Jednak wynik jest łatwo uzyskany w razie potrzeby:auto res = fut.get(); // is an int
-
Możemy teraz rozważyć, jak zaimplementować coś w rodzaju
async
, ale w sposób, którymy kontrolujemy. Na przykład, możemy nalegać, aby funkcja była wykonywana w osobnym wątku. Wiemy już, że możemy dostarczyć osobny wątek za pomocąstd::thread
klasy.Następny niższy poziom abstrakcji robi dokładnie to:
std::packaged_task
. Jest to szablon, który zawija funkcję i zapewnia przyszłość dla zwracanej wartości funkcji, ale sam obiekt można wywołać, a wywołanie go jest według uznania użytkownika. Możemy ustaw to tak:std::packaged_task<int(double, char, bool)> tsk(foo); auto fut = tsk.get_future(); // is a std::future<int>
Przyszłość staje się gotowa, gdy wywołamy zadanie i połączenie się zakończy. Jest to idealne zadanie dla oddzielnego wątku. Musimy tylko upewnić się, że przeniesie zadanie do wątku:
std::thread thr(std::move(tsk), 1.5, 'x', false);
Wątek zaczyna działać natychmiast. Możemy albo
detach
it, albo miećjoin
it na końcu zakresu, lub kiedykolwiek (np. używając wrappera Anthony ' ego Williamsascoped_thread
, który naprawdę powinien być w bibliotece standardowej). Szczegóły korzystaniastd::thread
nie martw się o nas tutaj, ale pamiętaj, aby dołączyć lub odłączyć {30]} W końcu. Ważne jest to, że gdy wywołanie funkcji zakończy się, nasz wynik jest gotowy: {]}auto res = fut.get(); // as before
-
Teraz jesteśmy na najniższym poziomie: jak zaimplementowalibyśmy zadanie spakowane? To tutaj
std::promise
wchodzi. Obietnica jest podstawą komunikacji z przyszłością. Główne kroki to:Wątek wywołujący sprawia, że obiecuję.
Wątek wywołujący uzyskuje przyszłość z obietnicy.
Obietnica, wraz z argumentami funkcji, są przenoszone do osobnego wątku.
Nowy wątek wykonuje funkcję i wypełnia obietnicę.
Oryginalny wątek pobiera wynik.
template <typename> class my_task; template <typename R, typename ...Args> class my_task<R(Args...)> { std::function<R(Args...)> fn; std::promise<R> pr; // the promise of the result public: template <typename ...Ts> explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { } template <typename ...Ts> void operator()(Ts &&... ts) { pr.set_value(fn(std::forward<Ts>(ts)...)); // fulfill the promise } std::future<R> get_future() { return pr.get_future(); } // disable copy, default move };
Użycie tego szablonu jest zasadniczo takie samo jak w przypadku
std::packaged_task
. Zwróć uwagę, że przeniesienie całego zadania ustępuje przesunięciu obietnicy. W bardziej doraźnych sytuacjach można również przenieść obiekt promise bezpośrednio do nowego wątku i uczynić go argumentem funkcji funkcji wątku, ale owijarka zadań, taka jak ta powyżej, wydaje się bardziej elastycznym i mniej inwazyjnym rozwiązaniem.
Tworzenie WYJĄTKÓW
Obietnice są ściśle związane z wyjątkami. Sam interfejs obietnicy nie wystarczy, aby przekazać jego stan całkowicie, więc wyjątki są wyrzucane, gdy operacja na obietnicy nie ma sensu. Wszystkie wyjątki są typustd::future_error
, który wywodzi się z std::logic_error
. Po pierwsze, opis niektórych ograniczeń:
Domyślnie skonstruowana obietnica jest nieaktywna. Nieaktywne obietnice mogą umrzeć bez konsekwencji.
Obietnica staje się aktywna, gdy przyszłość jest uzyskiwana przez
get_future()
. Jednak tylko jedna przyszłość może być uzyskana!A obietnica musi być spełniona przez
set_value()
lub mieć wyjątek ustawiony przezset_exception()
przed zakończeniem jej życia, jeśli jej przyszłość ma zostać skonsumowana. Spełniona obietnica może umrzeć bez konsekwencji, a[17] staje się dostępna w przyszłości. Obietnica z wyjątkiem wywoła zapisany wyjątek po wywołaniuget()
w przyszłości. Jeśli obietnica umrze bez wartości ani wyjątku, wywołanieget()
w przyszłości wywoła wyjątek "broken promise".
Oto mały test serii, aby zademonstrować te różne wyjątkowe zachowania. Najpierw uprząż:
#include <iostream>
#include <future>
#include <exception>
#include <stdexcept>
int test();
int main()
{
try
{
return test();
}
catch (std::future_error const & e)
{
std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl;
}
catch (std::exception const & e)
{
std::cout << "Standard exception: " << e.what() << std::endl;
}
catch (...)
{
std::cout << "Unknown exception." << std::endl;
}
}
Przejdźmy do testów.
Przypadek 1: nieaktywna obietnica
int test()
{
std::promise<int> pr;
return 0;
}
// fine, no problems
Przypadek 2: aktywna obietnica, nieużywana
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
return 0;
}
// fine, no problems; fut.get() would block indefinitely
Przypadek 3: zbyt wiele kontraktów futures
int test()
{
std::promise<int> pr;
auto fut1 = pr.get_future();
auto fut2 = pr.get_future(); // Error: "Future already retrieved"
return 0;
}
Przypadek 4: spełniona obietnica
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
{
std::promise<int> pr2(std::move(pr));
pr2.set_value(10);
}
return fut.get();
}
// Fine, returns "10".
Przypadek 5: zbyt wiele satysfakcji
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
{
std::promise<int> pr2(std::move(pr));
pr2.set_value(10);
pr2.set_value(10); // Error: "Promise already satisfied"
}
return fut.get();
}
Ten sam wyjątek jest wyrzucany, jeśli jest więcej niż jeden z albo z set_value
albo set_exception
.
Przypadek 6: Wyjątek
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
{
std::promise<int> pr2(std::move(pr));
pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo")));
}
return fut.get();
}
// throws the runtime_error exception
Przypadek 7: złamana obietnica
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
{
std::promise<int> pr2(std::move(pr));
} // Error: "broken promise"
return fut.get();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-21 10:06:08
C++ dzieli implementację futures na zbiór z małych bloków
Std::promise jest jedną z tych części.
Obietnica jest nośnikiem przekazującym wartość zwracaną (lub wyjątek) od wątku wykonującego funkcję do wątku to wpływa na przyszłość funkcji.
...
Przyszłość to obiekt synchronizacji zbudowany wokół odbieranie końca kanału promise.
Więc, jeśli chcesz użyć przyszłości, kończysz z obietnicą, której używasz, aby uzyskać wynik przetwarzania asynchronicznego.
Przykład ze strony to:
promise<int> intPromise;
future<int> intFuture = intPromise.get_future();
std::thread t(asyncFun, std::move(intPromise));
// do some other stuff
int result = intFuture.get(); // may throw MyException
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-12 20:34:59
W przybliżonym przybliżeniu można rozważyć std::promise
jako drugi koniec std::future
(jest to fałsz , ale dla ilustracji można myśleć tak, jakby był). Koniec kanału komunikacyjnego używałby std::future
do konsumowania danych ze stanu współdzielonego, podczas gdy wątek producenta używałby std::promise
do zapisu do stanu współdzielonego.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-12 20:30:27
std::promise
jest kanałem lub ścieżką dla informacji, które mają być zwrócone z funkcji asynchronicznej. std::future
jest mechanizmem synchronizacji, który sprawia, że wywołujący czeka, aż wartość zwracana w std::promise
będzie gotowa(co oznacza, że jej wartość jest ustawiona wewnątrz funkcji).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-12 20:42:10
W asynchronicznym przetwarzaniu są tak naprawdę 3 podstawowe byty. C++11 obecnie skupia się na 2 z nich.
Podstawowe rzeczy, których potrzebujesz do asynchronicznie uruchomić logikę to:
- zadanie (logika spakowana jako jakiś obiekt functora), które będzie działać 'gdzieś'.
- rzeczywisty węzeł przetwarzania - wątek, proces itp. który uruchamia takie funktory, gdy są do niego dostarczone. Spójrz na wzorzec projektowy "Command", aby uzyskać dobry pomysł na to, jak robi to basic worker thread pool.
- uchwyt wyniku : ktoś potrzebuje tego wyniku i potrzebuje obiektu, który go za niego otrzyma. Dla OOP i innych powodów, każde oczekiwanie lub synchronizacja powinny być wykonywane w interfejsach API tego uchwytu.
C++11 nazywa rzeczy, o których mówię w (1) std::promise
, a te w (3) std::future
.
{[2] } jest jedyną rzeczą udostępnioną publicznie dla (2). Jest to niefortunne, ponieważ prawdziwe programy muszą zarządzać zasobami wątków i pamięci, a większość będzie chciała zadania uruchamiane na pulach wątków zamiast tworzenia i niszczenia wątku dla każdego małego zadania (co prawie zawsze powoduje niepotrzebne uderzenia wydajności same w sobie i może łatwo spowodować głód zasobów, który jest jeszcze gorszy).
Według Herba Suttera i innych w C++11 brain trust, istnieją wstępne plany dodania std::executor
, która-podobnie jak w Javie - będzie podstawą puli wątków i logicznie podobnych konfiguracji dla (2). Może zobaczymy to w C++2014, ale mój zakład jest bardziej jak C++17 (i niech Bóg ma nas w opiece, jeśli zepsują dla nich standard).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-28 08:10:42
A std::promise
jest tworzony jako punkt końcowy dla pary obietnica / przyszłość, a std::future
(utworzony z std:: promise przy użyciu metody get_future()
) jest drugim punktem końcowym. Jest to prosta, jednokrotna metoda zapewnienia sposobu synchronizacji dwóch wątków, ponieważ jeden wątek dostarcza dane do innego wątku za pośrednictwem wiadomości.
Można myśleć o tym, jak jeden wątek tworzy obietnicę dostarczenia danych, a drugi wątek zbiera obietnicę w przyszłości. Mechanizm ten może być używany tylko raz.
Mechanizm obietnicy / przyszłości jest tylko jednym kierunkiem, od wątku, który używa metody set_value()
z std::promise
do wątku, który używa get()
z std::future
do odbioru danych. Wyjątek jest generowany, jeśli metoda get()
przyszłości zostanie wywołana więcej niż raz.
Jeśli wątek z std::promise
nie użył set_value()
, aby spełnić swoją obietnicę, wtedy gdy drugi wątek wywoła get()
z std::future
, aby zebrać obietnicę, drugi wątek przejdzie w stan oczekiwania do momentu, gdy obietnica jest spełniona przez pierwszy wątek z std::promise
, gdy używa metody set_value()
do wysłania danych.
Poniższy przykładowy kod, prosta aplikacja konsoli Visual Studio 2013 Windows, pokazuje przy użyciu kilku klas/szablonów współbieżności C++11 i innych funkcji. Ilustruje użycie obietnicy / przyszłości, które działa dobrze, autonomiczne wątki, które wykonają pewne zadanie i zatrzymają się, oraz użycie, w którym wymagane jest bardziej synchroniczne zachowanie i ze względu na potrzebę wielu powiadomienia para obietnica / przyszłość nie działa.
Jedna uwaga na temat tego przykładu to opóźnienia dodane w różnych miejscach. Opóźnienia te zostały dodane tylko po to, aby upewnić się, że różne wiadomości wydrukowane na konsoli za pomocą std::cout
będą jasne i że tekst z kilku wątków nie będzie mieszany.
Pierwsza część main()
to tworzenie trzech dodatkowych wątków i używanie std::promise
i std::future
do przesyłania danych między wątkami. Ciekawym punktem jest miejsce, w którym głównym thread uruchamia wątek, T2, który będzie czekał na dane z głównego wątku, coś zrobił, a następnie wysłał dane do trzeciego wątku, T3, który następnie coś zrobi i wyśle dane z powrotem do głównego wątku.
Druga część main()
tworzy dwa wątki i zestaw kolejek, aby umożliwić wiele wiadomości z głównego wątku do każdego z dwóch utworzonych wątków. Nie możemy do tego użyć std::promise
i std::future
, ponieważ Duet promise / future jest jednym strzałem i nie może być użyty wielokrotnie.
Źródło dla klasy Sync_queue
pochodzi z Stroustrup ' s the C++ Programming Language: 4th Edition.
// cpp_threads.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <thread> // std::thread is defined here
#include <future> // std::future and std::promise defined here
#include <list> // std::list which we use to build a message queue on.
static std::atomic<int> kount(1); // this variable is used to provide an identifier for each thread started.
//------------------------------------------------
// create a simple queue to let us send notifications to some of our threads.
// a future and promise are one shot type of notifications.
// we use Sync_queue<> to have a queue between a producer thread and a consumer thread.
// this code taken from chapter 42 section 42.3.4
// The C++ Programming Language, 4th Edition by Bjarne Stroustrup
// copyright 2014 by Pearson Education, Inc.
template<typename Ttype>
class Sync_queue {
public:
void put(const Ttype &val);
void get(Ttype &val);
private:
std::mutex mtx; // mutex used to synchronize queue access
std::condition_variable cond; // used for notifications when things are added to queue
std::list <Ttype> q; // list that is used as a message queue
};
template<typename Ttype>
void Sync_queue<Ttype>::put(const Ttype &val) {
std::lock_guard <std::mutex> lck(mtx);
q.push_back(val);
cond.notify_one();
}
template<typename Ttype>
void Sync_queue<Ttype>::get(Ttype &val) {
std::unique_lock<std::mutex> lck(mtx);
cond.wait(lck, [this]{return !q.empty(); });
val = q.front();
q.pop_front();
}
//------------------------------------------------
// thread function that starts up and gets its identifier and then
// waits for a promise to be filled by some other thread.
void func(std::promise<int> &jj) {
int myId = std::atomic_fetch_add(&kount, 1); // get my identifier
std::future<int> intFuture(jj.get_future());
auto ll = intFuture.get(); // wait for the promise attached to the future
std::cout << " func " << myId << " future " << ll << std::endl;
}
// function takes a promise from one thread and creates a value to provide as a promise to another thread.
void func2(std::promise<int> &jj, std::promise<int>&pp) {
int myId = std::atomic_fetch_add(&kount, 1); // get my identifier
std::future<int> intFuture(jj.get_future());
auto ll = intFuture.get(); // wait for the promise attached to the future
auto promiseValue = ll * 100; // create the value to provide as promised to the next thread in the chain
pp.set_value(promiseValue);
std::cout << " func2 " << myId << " promised " << promiseValue << " ll was " << ll << std::endl;
}
// thread function that starts up and waits for a series of notifications for work to do.
void func3(Sync_queue<int> &q, int iBegin, int iEnd, int *pInts) {
int myId = std::atomic_fetch_add(&kount, 1);
int ll;
q.get(ll); // wait on a notification and when we get it, processes it.
while (ll > 0) {
std::cout << " func3 " << myId << " start loop base " << ll << " " << iBegin << " to " << iEnd << std::endl;
for (int i = iBegin; i < iEnd; i++) {
pInts[i] = ll + i;
}
q.get(ll); // we finished this job so now wait for the next one.
}
}
int _tmain(int argc, _TCHAR* argv[])
{
std::chrono::milliseconds myDur(1000);
// create our various promise and future objects which we are going to use to synchronise our threads
// create our three threads which are going to do some simple things.
std::cout << "MAIN #1 - create our threads." << std::endl;
// thread T1 is going to wait on a promised int
std::promise<int> intPromiseT1;
std::thread t1(func, std::ref(intPromiseT1));
// thread T2 is going to wait on a promised int and then provide a promised int to thread T3
std::promise<int> intPromiseT2;
std::promise<int> intPromiseT3;
std::thread t2(func2, std::ref(intPromiseT2), std::ref(intPromiseT3));
// thread T3 is going to wait on a promised int and then provide a promised int to thread Main
std::promise<int> intPromiseMain;
std::thread t3(func2, std::ref(intPromiseT3), std::ref(intPromiseMain));
std::this_thread::sleep_for(myDur);
std::cout << "MAIN #2 - provide the value for promise #1" << std::endl;
intPromiseT1.set_value(22);
std::this_thread::sleep_for(myDur);
std::cout << "MAIN #2.2 - provide the value for promise #2" << std::endl;
std::this_thread::sleep_for(myDur);
intPromiseT2.set_value(1001);
std::this_thread::sleep_for(myDur);
std::cout << "MAIN #2.4 - set_value 1001 completed." << std::endl;
std::future<int> intFutureMain(intPromiseMain.get_future());
auto t3Promised = intFutureMain.get();
std::cout << "MAIN #2.3 - intFutureMain.get() from T3. " << t3Promised << std::endl;
t1.join();
t2.join();
t3.join();
int iArray[100];
Sync_queue<int> q1; // notification queue for messages to thread t11
Sync_queue<int> q2; // notification queue for messages to thread t12
std::thread t11(func3, std::ref(q1), 0, 5, iArray); // start thread t11 with its queue and section of the array
std::this_thread::sleep_for(myDur);
std::thread t12(func3, std::ref(q2), 10, 15, iArray); // start thread t12 with its queue and section of the array
std::this_thread::sleep_for(myDur);
// send a series of jobs to our threads by sending notification to each thread's queue.
for (int i = 0; i < 5; i++) {
std::cout << "MAIN #11 Loop to do array " << i << std::endl;
std::this_thread::sleep_for(myDur); // sleep a moment for I/O to complete
q1.put(i + 100);
std::this_thread::sleep_for(myDur); // sleep a moment for I/O to complete
q2.put(i + 1000);
std::this_thread::sleep_for(myDur); // sleep a moment for I/O to complete
}
// close down the job threads so that we can quit.
q1.put(-1); // indicate we are done with agreed upon out of range data value
q2.put(-1); // indicate we are done with agreed upon out of range data value
t11.join();
t12.join();
return 0;
}
Ta prosta aplikacja tworzy następujące wyjście.
MAIN #1 - create our threads.
MAIN #2 - provide the value for promise #1
func 1 future 22
MAIN #2.2 - provide the value for promise #2
func2 2 promised 100100 ll was 1001
func2 3 promised 10010000 ll was 100100
MAIN #2.4 - set_value 1001 completed.
MAIN #2.3 - intFutureMain.get() from T3. 10010000
MAIN #11 Loop to do array 0
func3 4 start loop base 100 0 to 5
func3 5 start loop base 1000 10 to 15
MAIN #11 Loop to do array 1
func3 4 start loop base 101 0 to 5
func3 5 start loop base 1001 10 to 15
MAIN #11 Loop to do array 2
func3 4 start loop base 102 0 to 5
func3 5 start loop base 1002 10 to 15
MAIN #11 Loop to do array 3
func3 4 start loop base 103 0 to 5
func3 5 start loop base 1003 10 to 15
MAIN #11 Loop to do array 4
func3 4 start loop base 104 0 to 5
func3 5 start loop base 1004 10 to 15
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-22 22:51:42
Obietnica to drugi koniec drutu.
Wyobraź sobie, że musisz odzyskać wartość future
obliczoną przez async
. Jednak nie chcesz, aby był on obliczany w tym samym wątku, a nawet nie tworzysz wątku "teraz" - być może twoje oprogramowanie zostało zaprojektowane tak, aby wybrać wątek z puli, więc nie wiesz Kto {16]} wykona obliczenia che w końcu.
Co PRZEKAZUJESZ do tego (jeszcze nieznanego) wątku / klasy / encji? Nie zdasz future
, ponieważ jest to wynik . Chcesz przekazać coś, co jest podłączone do future
i to reprezentuje drugi koniec przewodu, więc po prostu zapytasz future
bez wiedzy, kto faktycznie coś obliczy/napisze.
To jest promise
. Jest to uchwyt podłączony do twojego future
. Jeśli future
jest głośnikiem , a z get()
zaczynasz słuchać, dopóki nie wyjdzie jakiś dźwięk, promise
jest mikrofonem ; ale nie byle jaki mikrofon, jest to mikrofon podłączony jednym przewodem do trzymanego głośnika. Możesz wiedzieć, kto jest na drugim końcu, ale nie musisz tego wiedzieć - po prostu dajesz i czekasz, aż druga strona coś powie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-29 21:24:24