惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
L
LangChain Blog
A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
博客园 - 三生石上(FineUI控件)
N
Netflix TechBlog - Medium
D
DataBreaches.Net
P
Proofpoint News Feed
小众软件
小众软件
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
雷峰网
雷峰网
G
Google Developers Blog

The Old New Thing

Magic statics vs. std::call_once - The Old New Thing Why do Microsoft job levels start in the high 50's instead of starting at a sane number like 1? - The Old New Thing Why didn't Read­Directory­ChangesW provide a way to correlate the two sides of a rename operation? - The Old New Thing How can I remove the Close button from my window caption? - The Old New Thing Why is the x86 undefined instruction called ud2? Why 2? - The Old New Thing What algorithm did Windows XP use to choose your initial user picture? - The Old New Thing A sample use of the winstart.bat file in Windows 95 - The Old New Thing Why don't we allow stacks to be sparse, instead of forcing them to be contiguous? - The Old New Thing What happens if you change a window class's GCL_CB­WND­EXTRA? - The Old New Thing The perils of binding to value types in XAML - The Old New Thing Microspeak: Funded / unfunded - The Old New Thing AWE does not require PAE, though PAE makes it much more useful - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 2 - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 1 - The Old New Thing In the product end game, every change carries significant risk, episode 2 - The Old New Thing Why didn't the Windows Entertainment Pack just run the MS-DOS version inside an emulator? - The Old New Thing Comparing the two holograms on the Windows 95 box - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function, practical exam - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function - The Old New Thing On wrapping a callable in a lambda that just calls it with the same parameters - The Old New Thing Why did the Microsoft Entertainment Pack for Windows have a special sticker announcing that it also had Tetris? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing Forcing an ARM64X executable to run as a specific architecture - The Old New Thing A little helper class for managing LPPROC_THREAD_ATTRIBUTE_LISTs - The Old New Thing The comments that go into code versus those that go into the pull request description - The Old New Thing The little-known winstart.bat batch file - The Old New Thing How can I perform a Copy­File&shy in unbuffered mode? - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5 - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4 - The Old New Thing
The case of the progress callback that never got called w...
Raymond Chen · 2026-09-03 · via The Old New Thing

A colleague was trying to figure out why their progress handler wasn’t being called.

// C#

async Task<bool> DownloadItemAsync(string id)
{
    var op = item.DownloadAsync(id);
    op.Progress += (s, pct) UpdateProgress(pct);
    var result = await op;
    ClearProgress();
    return result;
}

This is pretty standard stuff. Start the operation, hook up the progress, and then wait for the operation to complete. But they never got any progress.

I asked them to check if maybe the item was downloading so fast that they missed all the progress. But no, even if the download takes a long time, they never get any progress.

I suggested that they step through the DownloadAsync method to see where it raises progress, and then follow the execution to the point where the progress callback is supposed to be invoked, to see why it didn’t make it. (To be fair, this is a cross-language debugging problem, so it’s harder than it looks. I suggested just focusing on the C++ side: Wait for the COM-callable wrapper to be generated and set as the progress callback, and then set a breakpoint on that wrapper. If that breakpoint gets hit, but the C# code doesn’t run, then there is a problem in the projection. If the breakpoint never gets hit, then the problem is on the C++ side.)

My colleague came back with the answer. Here’s the code for DownloadAsync:

// C++/WinRT

winrt::IAsyncOperationWithProgress<bool, double>
    AggregateSource::DownloadAsync(winrt::hstring id)
{
    std::wstring_view idview { id };
    auto pos = idview.find(L':');
    if (pos == std::wstring_view::npos) {
        co_return false;
    }

    auto providerId = Unescape(idview.substr(0, pos - 1));

    auto provider = GetProvider(providerId);
    if (!provider) {
        co_return false;
    }

    auto providerItemId = Unescape(idview.substr(pos + 1));
    co_return co_await provider.DownloadAsync(providerItemId);
}

The AggregateSource gathers items from multiple providers. The format of the id is a provider, a colon, and then an ID. (The provider ID and item ID are escaped, just in case they themselves happen to contain a colon.)

We look up the provider, and then ask the provider to download the item.

Do you see the problem?

The DownloadAsync does not generate any progress reports!

It never calls co_await winrt::get_progress_token(), much less call the token with a progress value to generate a progress report.

It’s apparent that what the code wants to do when it attaches the progress callback is to receive callbacks from the inner operation, the one that comes from the provider. However, the only IAsyncOperationWithProgress that it has access to is the one returned by the AggregateSource::DownloadAsync method.

The easy solution here is to get rid of the middle man and just return the provider’s IAsyncOperationWithProgress. That way, the caller can connect to the underlying operation’s progress.

winrt::IAsyncOperationWithProgress<bool, double>
    AggregateSource::DownloadAsync(winrt::hstring id)
{
    std::wstring_view idview { id };
    auto pos = idview.find(L':');
    if (pos == std::wstring_view::npos) {
        return completed_async(false);
    }

    auto providerId = Unescape(idview.substr(0, pos - 1));

    auto provider = GetProvider(providerId);
    if (!provider) {
        return completed_async(false);
    }

    auto providerItemId = Unescape(idview.substr(pos + 1));
    return provider.DownloadAsync(providerItemId);
}

If you don’t believe in completed_async, you can just write

        return [] -> winrt::IAsyncOperationWithProgress<bool, double> {
            return false;
        }();

I said that this is the easy solution. There’s also a hard solution, which we will have to look at later because I haven’t written it up yet.

Category

Topics

Author

Raymond Chen

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.