0

I'm developing a game. Lets say theres an object LoadingState with these methods (and some others):

  • create
  • update
  • load

The update is called every time the CPUs clock ticks, whilst the create will be called only once and it should call the load function (asynchronously), in order to load some game assets. Calling load asynchronously inside the create function allows (in theory) update from beling called while the create/load executes. This, however, is not happening.

Prior to Visual Studio 2015, I was using std::async like that:

std::async(&LoadingState::load, this, THE_ASSETS_PATHS_STRING_VECTOR);

After migrating to Visual Studio 2015 (C++17) and reading that the std::launch has to be specified, otherwise an unexpected behavior might happen, the async is now called like that:

std::async(std::launch::async, &LoadingState::load, this, THE_ASSETS_PATHS_STRING_VECTOR);

In other words, it looks like to me that 'this' gets locked by the std::async, blocking the whole object, preventing the main thread to call update.

More relevant code:

void LoadingState::onCreate()
{
    std::vector<std::string> assets;

    assets.push_back("Images/Scenario/wall.png");
    assets.push_back("Images/Scenario/bigdummy.png");
    assets.push_back("Images/Scenario/buildings.png");
    assets.push_back("Images/Scenario/floor.png");
    assets.push_back("Images/Scenario/terrain.png");
    assets.push_back("Images/Scenario/trees.png");

    // Load assets asynchronously
    std::async(std::launch::async, &LoadingState::load, this, assets);
}

void LoadingState::load(std::vector<std::string> assets)
{
    unsigned int count = 0;
    unsigned int ratio = 100U / assets.size();

    // Cache the asset accordingly
    for (auto& asset : assets)
    {
        // Load and store the asset
        // ...

        // Asset loaded
        count++;

        // Calculate the progress by count
        m_progress = count * ratio;
    }

    // If assets were skipped or progress would be like 98% due to truncation, normalize it
    m_progress = 100U;
}


void LoadingState::update(float delta)
{
    // ...

    // If finished loading the resources, move on to playing state!
    if (m_progress >= 100U) {
        m_machine->next(new PlayingState(m_machine));
    }
}

What am I misunderstanding here?!

PS: Everything used to run smothly prior to the migration.

Yves Calaci
  • 1,019
  • 1
  • 11
  • 37
  • 2
    Priort to C++11, I promise you that you were not using `std::async`! – Kerrek SB Jul 14 '16 at 18:32
  • Neither does THE_ASSETS_PATHS_STRING_VECTOR exist. Your criticism/sarcasm did not help. – Yves Calaci Jul 14 '16 at 18:39
  • 1
    "*Visual Studio 2015 (C++17)*" VS 2015 does not contain C++17. It contains bits of C++17 *at most*. – Nicol Bolas Jul 14 '16 at 18:43
  • 7
    Unless you capture the `std::future` returned by `std::async`, it will _always_ block. – Tim Jul 14 '16 at 18:44
  • @Tim I kinda noticed this, but wasnt sure. I couldnt find any doc about this, though. Where did you see it? – Yves Calaci Jul 14 '16 at 18:45
  • @YvesHenri See the 3rd paragraph of 'notes' here http://en.cppreference.com/w/cpp/thread/async – kfsone Jul 14 '16 at 18:49
  • 1
    @YvesHenri Actually, I was mistaken. The standard doesn't require it to block, but that it may. See 30.6.8.5 [futures.async] in N4296. – Tim Jul 14 '16 at 18:50
  • See also http://stackoverflow.com/questions/21531096/can-i-use-stdasync-without-waiting-for-the-future-limitation – kfsone Jul 14 '16 at 18:50

1 Answers1

2
std::async(std::launch::async, &LoadingState::load, this, assets);

The future returned by async will block in its destructor until the async function has finished (unlike every other future object). You therefore must capture that future and keep it alive (moving it where necessary) until you're ready for the answer.

Or you can just stop using il-conceived features like async.

reading that the std::launch has to be specified

No, it does not.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    `async` wasn't ill-conceived. It just wasn't given proper defaults. C++14 fixed that. You quoted the OP out of context: "std::launch has to be specified, otherwise an unexpected behavior might happen." This is true as of C++14. – Tim Jul 14 '16 at 18:53
  • "If neither std::launch::async nor std::launch::deferred, nor any implementation-defined policy flag is set in policy, the behavior is undefined." - http://en.cppreference.com/w/cpp/thread/async – Yves Calaci Jul 14 '16 at 18:55
  • 1
    @YvesHenri: And calling the first overload will call the second overload, providing both of those parameters. That's not undefined behavior; that's just a default parameter via function overloading. – Nicol Bolas Jul 14 '16 at 18:56
  • Anyways, theres no other way to use async without storing a future? That seems odd to keep it if I'm not dealing with it directly, other than letting async run asynchronously. – Yves Calaci Jul 14 '16 at 18:58
  • @YvesHenri: "*theres no other way to use async without storing a future?*" [No](http://stackoverflow.com/questions/21531096). That's one reason I called it "il-conceived". – Nicol Bolas Jul 14 '16 at 19:00