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

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
J
Java Code Geeks
I
InfoQ
V
Visual Studio Blog
M
MIT News - Artificial intelligence
H
Help Net Security
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
G
Google Developers Blog
A
About on SuperTechFans
腾讯CDC
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
云风的 BLOG
云风的 BLOG
S
SegmentFault 最新的问题
WordPress大学
WordPress大学

The Old New Thing

std::call_once vs. std::async - 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 case of the progress callback that never got called when progress happened - 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
Cancellation of Windows Runtime activities is asynchronou...
Raymond Chen · 2026-06-24 · via The Old New Thing

In the Windows Runtime, there are four interface patterns for representing asynchronous activity.

  No return type With return type T
Without progress IAsyncAction IAsyncOperation<T>
With progress IAsyncActionWithProgress<P> IAsyncOperationWithProgress<T, P>

For the purpose of this discussion, I will collectively call these “asynchronous activities”.

One of the things you can do with asynchronous activities is cancel them, by calling the Cancel method. This method submits a request to cancel, but it does not wait for the operation to acknowledge the cancellation. If you want to wait for the operation to stop executing, you have to wait for it to call the completion callback.²

Asynchronous cancellation is important for avoiding deadlocks.

Most of the time, the scenarios involve cross-thread synchronous calls, but here’s an extremely obvious way it can happen.

Suppose that you have registered a progress callback on your asynchronous activity with progress.

// C#
async Task DoSomethingWithTimeoutAsync()
{
    var op = DoSomethingAsync();
    op.Progress = (sender, p) => {
        UpdateProgress(p);
        if (p >= 0.5) {
            sender.Cancel();
        }
    };
    try {
        await op;
    } catch (TaskCanceledException) {
        // ignore cancellation
    }
}

// C++/WinRT
winrt::fire_and_forget Widget::DoSomethingWithTimeoutAsync()
{
    auto op = DoSomethingAsync();
    op.Progress([&](auto&& sender, auto p) {
        this->UpdateProgress(p);
        if (p >= 0.5) {
            sender.Cancel();
        }
    });

    try {
        co_await op;
    } catch (winrt::hresult_canceled const&) {
        // ignore cancellation
    }
    co_return;
}

The code calls DoSomethingAsync() and attaches a progress callback which cancels the operation once the progress reaches 50%. If the Cancel() method waited for outstanding progress callbacks to completed, you have a deadlock: The Cancel() is waiting for the progress callback to complete. But the progress callback is itself calling Cancel()

To avoid deadlocks when cancellation occurs while a progress callback is in progress, the cancellation method doesn’t wait for an acknowledgment. If you want to know when the activity is finished, wait for it to complete. If you want to ignore progress reports that arrive after you cancel, you can do that yourself.

// C#

async Task DoSomethingWithTimeoutAsync()
{
    var op = DoSomethingAsync();
    bool canceled = false;
    op.Progress = (sender, p) => {
        if (!canceled) {
            UpdateProgress(p);
            if (p >= 0.5) {
                canceled = true;
                sender.Cancel();
            }
        }
    };
    try {
        await op;
    } catch (TaskCanceledException) {
        // ignore cancellation
    }
}

// C++/WinRT

winrt::fire_and_forget Widget::DoSomethingWithTimeoutAsync()
{
    auto op = DoSomethingAsync();
    bool canceled = false;
    op.Progress([&](auto&& sender, auto p) {
        if (!canceled) {
            this->UpdateProgress(p);
            if (p >= 0.5) {
                canceled = true;
                sender.Cancel();
            }
        }
    });

    try {
        co_await op;
    } catch (winrt::hresult_canceled const&) {
        // ignore cancellation
    }
    co_return;
}

(The canceled variable doesn’t need to be atomic because progress callbacks do not overlap.)

Notice in the C++/winRT version that even after we call Cancel(), we wait for the co_await op to report completion before we return. Otherwise, the Progress callback will access an already-destroyed canceled variable.

¹ This is also the cancellation model for I/O and RPC: The cancellation method submits a cancellation request and returns immediately, and the underlying operation indicates that it has stopped executing by reporting some sort of completion.

² You might try to solve this by saying “Cancellation is asynchronous if the Cancel is issued from the same thread as the progress event”, but that doesn’t help in this case, which is more realistic:

// C#
async void CancelAfter(IAsyncInfo op, TimeSpan delay)
{
    co_await Task.Delay(delay);
    op.Cancel();
}

async Task DoSomethingWithTimeoutAsync()
{
    var op = DoSomethingAsync();
    op.Progress = (sender, p) => {
        Invoke(() => UpdateProgress(p));
    };
    CancelAfter(op, TimeSpan.FromSeconds(5));
    try {
        await op;
    } catch (TaskCanceledException) {
        // ignore cancellation
    }
}

Suppose the Progress event is raised on a background thread at 4.9999 seconds. Before the lambda can call Invoke(), the CancelAfterDelay timeout elapses, and the UI thread calls Cancel(). Now you have a deadlock because the Progress event is waiting for the lambda, the lambda is waiting for the Invoke, the Invoke is waiting for the UI thread, the UI thread is waiting for the Cancel, and the Cancel is waiting for the Progress event.

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.