microsoft / cppwinrt

C++/WinRT
MIT License
1.64k stars 238 forks source link

Best way to keep track of pending IAsyncActions #1430

Closed amtopel closed 1 month ago

amtopel commented 1 month ago

Version

No response

Summary

This isn't a bug. Just a question.

Let's say you have a long-running application that fires off a bunch of methods on the threadpool using winrt::resume_background(). And then when it's time to close the app, you'd like to wait for any in-flight tasks to finish up.

I understand that there are functions like winrt::when_all that allow you to wait on a bunch of IAsyncActions. Or you can loop through a vector of IAsyncActions and co_await them, as discussed here.

But my question is: what's the best strategy for keeping a hold on your pending IAsyncActions so that you know the ones to wait on when it's time to quit? Is there a specific class or function for that?

Granted, you could just store each and every IAsyncAction in a vector, but for a long-running app that may fire off hundreds or thousands of tasks, it seems like that vector would get really large and inefficient--especially since, as a practical matter, there are probably only one or two outstanding tasks to wait on when it's time to quit.

Perhaps there could be a class that acts like a vector for storing IAsyncActions, except it would remove each one upon completion. But that would monopolize the IAsyncAction's delegate, wouldn't it?

Or is it better to use a latch (i.e., reverse semaphore) in this situation?

Thanks for any information or guidance.

Reproducible example

No response

Expected behavior

No response

Actual behavior

No response

Additional comments

No response

sylveon commented 1 month ago

I have been doing the following:

std::mutex m_asyncSetLock;
std::unordered_set<winrt::Windows::Foundation::IAsyncAction> m_inflightAsync;

void Foo::registerInflightAsync(winrt::Windows::Foundation::IAsyncAction async)
{
    async.Completed({ this, &Foo::unregisterInflightAsync });

    std::scoped_lock guard(m_asyncSetLock);
    m_inflightAsync.insert(std::move(async));
}

void Foo::unregisterInflightAsync(const winrt::Windows::Foundation::IAsyncAction& async, winrt::Windows::Foundation::AsyncStatus status)
{
    std::scoped_lock guard(m_asyncSetLock);
    m_inflightAsync.erase(async);
}

It does, as you say, monopolize the Completed handler.

amtopel commented 1 month ago

Thanks a lot. So then how do you use it at the end of an application if you can’t co_await the actions?

sylveon commented 1 month ago

At the end of the application, I just hard-block with .get() on each action consecutively.

amtopel commented 1 month ago

Oh yeah, that makes sense. Thanks again.