Skip to content

99 Advanced The Hundred

The async That Never Started

the worker that clocks in only when you look

using namespace std::chrono_literals;

std::atomic<int> checked{0};

int scan_files() {
    std::cout << "scanner on thread " << std::this_thread::get_id() << "\n";
    for (int i = 0; i < 4; ++i, ++checked)
        std::this_thread::sleep_for(50ms);
    return checked;
}

int main() {
    std::cout << "main    on thread " << std::this_thread::get_id() << "\n";
    auto f = std::async(std::launch::deferred, scan_files);   // scan in the background
    std::this_thread::sleep_for(300ms);
    std::cout << "300 ms later:  checked = " << checked << "\n";
    int total = f.get();
    std::cout << "after f.get(): checked = " << total << "\n";
}

Run it. After 300 ms of background scanning, how many files are checked?

Answer

Zero — the scan hasn't begun. It starts inside f.get(), on main's own thread. Typical output (GCC 11.5 on x86-64 Linux):

main    on thread 140288006013312
300 ms later:  checked = 0
scanner on thread 140288006013312
after f.get(): checked = 4

Why

std::launch::deferred means what it says: the callable is boxed up, nothing starts, and the first get() or wait() runs it synchronously on the calling thread — hence the two identical ids. Never touch the future and the function is never invoked at all, unlike entry 100, where a real async future's destructor blocks until the task finishes. The trap is the default: plain std::async(f) means std::launch::async | std::launch::deferred, and the implementation picks — a conforming library may defer every time. Delete std::launch::deferred, and GCC 11.5's libstdc++ does start a real thread here (different id, checked = 4 at 300 ms), but that is a vendor's habit, not a promise, and nothing in -Wall -Wextra warns about any of it.

The fix

Say what you mean — std::launch::async must start a thread or throw std::system_error:

auto f = std::async(std::launch::async, scan_files);              // really concurrent
if (f.wait_for(0s) == std::future_status::deferred) { /* ... */ } // catch one in the act

Takeaway: with no launch policy, concurrency is optional — spell out std::launch::async, or you may have written a very ornate function call.

Try it: g++ -std=c++17 -pthread main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry