Search code examples
multithreadingc++11asynchronousmutexstdasync

C++17 async: running a this' method blocks the whole object


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.


Solution

  • 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.