11
votes

I'm using vc2011 and it turns out the std::async(std::launch::async, ... ) is a bit buggy (sometimes it does not spawn new threads and runs them in parallel, but instead reuses threads and runs task one after another). This is too slow when I'm doing expensive network calls. So I figured I'd write my own async function. I'm getting stuck though, where should std::promise live? In the 1) thread function, 2) async function, or 3) caller function.

Code:

#include <future>
#include <thread>
#include <iostream>
#include <string>
#include <vector>

std::string thFun() {
    throw std::exception("bang!");
    return "val";
}

std::future<std::string> myasync(std::promise<std::string>& prms) {
//std::future<std::string> myasync() {
    //std::promise<std::string> prms; //needs to outlive thread. How?

    std::future<std::string> fut = prms.get_future();
    std::thread th([&](){
        //std::promise<std::string> prms; //need to return a future before...
        try {
            std::string val = thFun();

            prms.set_value(val);

        } catch(...) {
            prms.set_exception(std::current_exception());
        }

     });

    th.detach();
    return fut;
}

 int main() {

    std::promise<std::string> prms; //I really want the promise hidden iway in the myasync func and not live here in caller code but the promise needs to outlive myasync and live as long as the thread. How do I do this?
    auto fut = myasync(prms);

    //auto fut = myasync(); //Exception: future already retrieved

    try {
        auto res = fut.get();
        std::cout << "Result: " << res << std::endl;

    } catch(const std::exception& exc) {
        std::cout << "Exception: " << exc.what() << std::endl;
    }

 }

I cant seem to get past the fact that the std::promise needs to outlive the async function (and live as long as the thread), so the promise cant live as a local variable in the async func. But the std::promise shouldn’t live in in the caller code either, as the caller only need to know about futures. And i dont know how to make the promise live in the thread function as async needs to return a future before it even calls the thread func. I’m scratching my head on this one.

Anyone got any ideas?

Edit: I'm highlighting this here as the top comment is a bit misinformed. While the default for std::asycn is allowed to be the dererred mode, when a launch policy of std::launch::async is explicitly set it must behave "as if" threads are spawned and run at once (see wording in en.cppreference.com/w/cpp/thread/async). See the example in pastebin.com/5dWCjjNY for one case where this is not the behavioured seen in vs20011. The solution works great and sped up my real world application by a factor of 10.

Edit 2: MS fixed the bug. More info here: https://connect.microsoft.com/VisualStudio/feedback/details/735731/std-async-std-launch-async-does-not-behave-as-std-thread

2
"sometimes it does not spawn new threads and runs them in parallel, but instead reuses threads and runs task one after another" That's not buggy; that's how it's allowed to work. There's no guarantee in the specification that any particular async call will run in a different thread from prior or future async calls. If you want that, then just create a bunch of threads, stick them in a container, and then join them when you want to get the data back.Nicol Bolas
In the future, please put your code directly in your question rather than linking to an external site.ildjarn
@petke: 'as if' is hugely important there. Is your behavior any different? Could you tell without looking at the source or debugging? If not, it was behaving 'as if' it had made a new thread.GManNickG
@Nicol, to your original question. The interface of async of nicer than std::thread so I would like to use it if possible. Using std::thread directly means you have to add intrusive boiler plate to functions to handle exceptions and return values.petke
@petke, your pastebin code doesn't technically prove anything by checking the thread ID, see [thread.thread.id]/2 "The library may reuse the value of a thread::id of a terminated thread that can no longer be joined." (However it's likely that the MS impl isn't reusing an ID and is using a pre-existing thread, since that's what they chose to do intentionally and have admitted was wrong and fixed it.)Jonathan Wakely

2 Answers

22
votes

Here is one solution:

future<string> myasync()
{
    auto prms = make_shared<promise<string>> ();

    future<string> fut = prms->get_future();

    thread th([=](){

        try {
            string val = thFun();
            // ...
            prms->set_value(val);

        } catch(...) {
            prms->set_exception(current_exception());
        }

     });

    th.detach();

    return fut;
}

Allocate promise on the heap, and then pass-by-value [=] a shared_ptr to it through to the lambda.

6
votes

You need to move the promise into the new thread. Andrew Tomazos's answer does it by creating a std::promise with shared ownership, so that both threads can own the promise, and when the current one returns from the current scope only the new thread owns the promise, i.e. the ownership has been transferred. But std::promise is movable so it should be possible to move it directly into the new thread, except that the "obvious" solution of capturing it doesn't work because lambda's can't capture by move, only by copy (or by reference, which wouldn't work as you'd get a dangling reference.)

However, std::thread supports passing rvalue objects to the new thread's start function. so you can declare the lambda to take a std::promise argument by value, i.e. pass the promise to the lambda rather than capturing it, and then move the promise into one of the arguments of the std::thread e.g

std::future<std::string> myasync() {
    std::promise<std::string> prms;

    std::future<std::string> fut = prms.get_future();
    std::thread th([&](std::promise<std::string> p){
        try {
            std::string val = thFun();

            p.set_value(val);

        } catch(...) {
            p.set_exception(std::current_exception());
        }

     }, std::move(prms));

    th.detach();
    return fut;
}

This move the promise into the std::thread object, which then moves it (in the context of the new thread) into the lambda's parameter p.