January 22nd, 2026
likemind blown3 reactions

A simple helper function for attaching a progress handler to a Windows Runtime IAsync­Action­With­Progress or IAsync­Operation­With­Progress

The Windows Runtime has interfaces IAsync­Action and IAsync­Operation<T> which represent asynchronous activity: The function starts the work and returns immediately, and then it calls you back when the work completes. Most language projections allow you to treat these as coroutines, so you can await or co_await them in order to suspend execution until the completion occurs.

There are also progress versions of these interfaces: IAsync­Action­With­Progress<P> and IAsync­Operation­With­Progress<T, P>. In addition to having a completion callback, you can also register a callback which will be called to inform you of the progress of the operation.

The usual usage pattern is

// C++/WinRT
auto operation = DoSomethingAsync();
operation.Progress([](auto&& op, auto&& p) { ⟦ ... ⟧ });
auto result = co_await operation;

// C++/CX
IAsyncOperation<R^, P>^ operation = DoSomethingAsync();
operation->Progress = ref new AsyncOperationProgressHandler<R^, P>(
    [](auto op, P p) { ⟦ ... ⟧ });
R^ result = co_await operation;

// C#
var operation = DoSomethingAsync();
operation.Progress += (op, p) => { ⟦ ... ⟧ };
var result = await operation;

// JavaScript
var result = await DoSomethingAsync()
                .then(null, null, p => { ⟦ ... ⟧ });

The JavaScript version is not too bad: You can attach the progress to the Promise and then await the whole thing. However, the other languages are fairly cumbersome because you have to declare an extra variable to hold the operation, so that you can attach the progress handler to it, and then await it. And in the C++ cases, having an explicitly named variable means that it is no longer a temporary, so instead of destructing at the end of the statement, it destructs when the variable destructs, which could be much later.

Here’s my attempt to bring the ergonomics of JavaScript to C++ and C#.

// C++/WinRT
template<typename Async, typename Handler>
Async const& with_progress(Async&& async, Handler&& handler)
{
    async.Progress(std::forward<Handler>(handler));
    return std::forward<Async>(async);
}

// C++/CX
template<typename Async, typename Handler>
Async with_progress(Async async, Handler handler)
{
    async->Progress = handler;
    return async;
}

// C#
static Async WithProgress<Async, Handler>(this Async async, Handler handler)
{
    async.Progress += handler;
    return async;
}

These functions don’t do much, but they save you the trouble of having to name your operations.

// C++/WinRT
auto result = co_await with_progress(DoSomethingAsync(),
            [](auto&& op, auto&& p) { ⟦ ... ⟧ });

// C++/CX
R^ result = co_await with_progress(DoSomethingAsync(),
            ref new AsyncOperationProgressHandler<R^, P>(
            [](auto op, P p) { ⟦ ... ⟧ });

// C#
var result = await DoSomethingAsync()
                .WithProgress((op, p) => { ⟦ ... ⟧ });
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

6 comments

Discussion is closed. Login to edit/delete existing comments.

Sort by :
  • LB

    I usually just do co_await std::exchange(operation, nullptr) or similar when I want to turn it back into a temporary again.

  • Frédéric B.

    I think the first C++/CX example should say `IAsync­Operation­With­Progress` rather than `IAsync­Operation­`.

  • Richard Deeming

    Maybe I’m missing something here, but if DoSomethingAsync reports progress before its first truly async await, wouldn’t that report get “lost”?

    The callback doesn’t get registered until the method returns. And assuming WRT is remotely similar to “normal” C# async methods, the method won’t return until the first await on an operation that doesn’t complete synchronously.

    • Neil Rashbrook

      I don’t know how progress reporting works, but all the example languages appear to allow some or all of the operation to complete synchronously, in the worst case, returning a resolved Promise thus preventing any progress reporting from happening. So I suspect it’s up to the function to await before trying to report any progress.

  • Valerie Luna Dickson · Edited

    The C# version won’t compile – neither type parameter is constrained in any way, so they’re all object to it. A proper C# version should look like this.

    static IAsyncActionWithProgress<T> WithProgress<T>(this IAsyncActionWithProgress<T> async, AsyncActionProgressHandler<T> handler)
    {
        async.Progress += handler;
        return async;
    }

    You’d need a second version for IAsyncOperationWithProgress<TResult, TProgress>, too, but it’d look pretty much the same.

  • Brett Thwaites Jr. · Edited

    I think you made the C# example too generic…