Asynchronous programming with futures and await
description
Transcript of Asynchronous programming with futures and await
Artur LaksbergMicrosoft May 8th, 2014
Asynchronous programming with futures and await
Agenda• Asynchrony with callbacks• Futures and Continuations• Composition with futures• await
Hollywood Principle
Don’t call us, we’ll call you!3
Read From FIle: Naïve SolutionReturn a string, block on call:string read_string_from_file(string file);
string s = read_string_from_file("myfile.txt");cout << s;
Problem: blocking call
Read From FIle: Callback SolutionDoes not return a string, takes a callback that accepts a string:template<typename Func>void read_string_from_file(string file, Func&& func);
...read_string_from_file("myfile.txt", [](string s) { cout << s;});
Concatenate Files: Naive SolutionRead from one file, then read from another:template<typename Func>void concatenate_files(string file1, string file2, Func&& func){ read_string_from_file(file1, [func](string str1) { read_string_from_file(file2, [func](string str2) { func(str1 + str2); }); });}
Concatenate Files: A “Better” Solutiontemplate<typename Func>void concatenate_files(string file1, string file2, Func&& func) {
auto results = make_shared<result_holder>();
read_string_from_file(file1, [=](string str) { if (results->str) { func(str + *results->str); } else{ results->str = make_unique<string>(str); } });
read_string_from_file(file2, [=](string str) { if (results->str) { func(*results->str + str); } else{ results->str = make_unique<string>(str); } });}
struct result_holder{ unique_ptr<string> str;};
? Spot the defect!
std::future<T>std::shared_future<T>
Long-Running WorkCounting lines in a file// Launch a task:future<int> work = async([] { return CountLinesInFile(…); });
// Collect the results:cout << work.get();
Problem: blocking call
Proposal: Continuation(shared_)future::then
// Launch a task:future<int> work = CountLinesInFileAsync(...);
work.then([] (future<int> f) { // Get the result cout << f.get();});
Binding Multiple Continuationsfuture<T1> t = async([](){ return func1();}).then ([](future<T1> n){ return func2(n.get());}).then ([](future<T2> n){ return func3(n.get());}).then ...
Advanced CompositionSequential Composition:
Parallel Composition (only shared_future):f.then(A).then(B);
f.then(A);f.then(B);
Join and Choice (more on next page):auto f = when_all(a, b);auto f = when_any(a, b);
JoinCreate a future that completes when all arguments completevector<future<int>> futures = get_futures();auto futureResult = when_all (begin(futures), end(futures)) .then([](future<vector<future<string>>> results) { for (auto& s : results.get() ) // will not block { cout << s.get(); // will not block } });
JoinNow with heterogeneous argumentsfuture<int> future1 = ...;future<string> future2 = ...;auto futureResult = when_all(future1, future2) .then([](future<tuple<future<int>, future<string>>> results) { auto pair = results.get(); // will not block ... }});
Concatenate Files: The Right SolutionTerse, efficient and safefuture<string> concatenate_files(string file1, string file2){ auto strings = when_all(read_string_from_file(file1), read_string_from_file(file2));
return strings.then([]( future<tuple<future<string>, future<string>>> strings) { auto pair = strings.get(); return pair.get<0>.get() + pair.get<1>.get(); });}
Concatenate Files: The Right SolutionTerse, efficient and safe (with polymorphic lambdas)future<string> concatenate_files(string file1, string file2){ auto strings = when_all(read_string_from_file(file1), read_string_from_file(file2));
return strings.then([]( auto strings) { auto pair = strings.get(); return pair.get<0>.get() + pair.get<1>.get(); });}
ChoiceCreate a future that completes when at least one of arguments completes:vector<future<int>> futures = get_futures();auto futureResult = when_any (begin(futures), end(futures)) .then([](future<vector<future<string>>> results) { for (auto& s : results.get() ) // will not block { if(s.ready()) cout << s.get(); // will not block } });
make_ready_futurefuture<int> compute(int x) { if (x < 0) return make_ready_future<int>(-1); if (x == 0) return make_ready_future<int>(0); future<int> f1 = async([]() { return do_work(x); }); return f1;}
is_readyfuture<int> f1 = async([]() { return possibly_long_computation(); });if(!f1.is_ready()) { //if not ready, attach a continuation and avoid a blocking wait fl.then([] (future<int> f2) { int v = f2.get(); process_value(v); });}// if ready, no need to add continuation, process value right away else { int v = f1.get(); process_value(v);}
awaitand resumable functions
Branches and loops
22
.get .thenstring read(string file){ istream fi = open(file).get(); string ret, chunk; while ((chunk = fi.read().get()).size()) ret += chunk; return ret;}
future<string> read(string file) { return open(file) .then([=](istream fi) { string ret, chunk; while (
???
Branches and loops
23
.get .thenstring read(string file){ istream fi = open(file).get(); string ret, chunk; while ((chunk = fi.read().get()).size()) ret += chunk; return ret;}
future<string> read(string file){ return open(file) .then([=](istream fi) { auto ret = make_shared<string>(); auto next = make_shared<function<future<string>()>>( [=] { fi.read() .then([=](string chunk) { if (chunk.size()) { *ret += chunk; return next(); } return make_ready_future(*ret); }); }); return (*next)(); });}
Await ExampleGet a value, convert to string:
future<string> f() resumable{ future<int> f1 = async([]() { return possibly_long_computation(); });
int n = await f1; return to_string(n);}
Branches and loops, with await
26
.get awaitstring read(string file){ istream fi = open(file).get(); string ret, chunk; while ((chunk = fi.read().get()).size()) ret += chunk; return ret;}
String read(string file){ istream fi = await open(file); string ret, chunk; while ((chunk = (await fi.read()).size()) ret += chunk; return ret;}
Thread #2Thread #1
Resumable Side Stack Simulation
27
Main Stack
………
……
foo();t = async_bar()
Side Stackasync_bar();
do_work();………
await async_work();
somefunc();bool b = t.get()
<blocked>
More sync calls…
return true;
<completed>
Side Stackasync_work();
do_work();await create_task();
Longrunning is done!<suspended
>return task<void>
<suspended>return task<bool>
return;
<completed>
void foo() { task<bool> t = async_bar();
somefunc();
bool b = t.get();}
task<bool> async_bar() resumable { do_work(); ... // sync calls
await async_work();
// More sync calls ... return true;}
task<void> async_work() resumable { do_work();
await create_task( [] { longrunning (); });
return;}
done!async_work
is done!
References• N3857: Improvements to std::future<T> and
Related APIs• N3858: Resumable Functions• N3970: Working Draft, Information technology –
Programming languages, their environments and system software interfaces – C++ Extensions for Concurrency
“Concurrent programs wait faster”
Tony Hoare