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

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
Forbes - Security
Forbes - Security
WordPress大学
WordPress大学
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
Spread Privacy
Spread Privacy
D
Darknet – Hacking Tools, Hacker News & Cyber Security
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
P
Privacy International News Feed
A
About on SuperTechFans
T
Tailwind CSS Blog
I
InfoQ
S
Securelist
云风的 BLOG
云风的 BLOG
罗磊的独立博客
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
B
Blog RSS Feed
V
Visual Studio Blog
Know Your Adversary
Know Your Adversary
The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
腾讯CDC
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
博客园 - 【当耐特】
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
S
Secure Thoughts
博客园 - 司徒正美
J
Java Code Geeks
Y
Y Combinator Blog
Google Online Security Blog
Google Online Security Blog
GbyAI
GbyAI
N
News and Events Feed by Topic
Help Net Security
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Project Zero
Project Zero
T
Tenable Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tor Project blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
小众软件
小众软件
K
Kaspersky official blog

The Old New Thing

Speculating on how the buggy control panel extension truncated a value that it had right in front of it - The Old New Thing The case of the invalid function pointer when shutting down the display control panel - The Old New Thing Microspeak: Double-click and drill down - The Old New Thing Why don't we just make the entire stack out of guard pages? - The Old New Thing The case of the mysterious changes to integers when there shouldn't have been any code generation effect - The Old New Thing I've decoded a #pragma detect_mismatch error and fixed the mismatch, but I still get the error - The Old New Thing The other kind of control flow guard check: The combined validate and call - The Old New Thing How did Windows 95 decide that a setup program ran? - The Old New Thing I opened a file with FILE_FLAG_DELETE_ON_CLOSE, but now I changed my mind - The Old New Thing How did we conclude that CcNamespace.dll was the ringleader of a group of DLLs that unloaded prematurely? - The Old New Thing The case of the thread executing from an unloaded third-party DLL - The Old New Thing It rather involved being on the other side of this airtight hatchway: Changing administrative settings - The Old New Thing 2026 mid-year link clearance - The Old New Thing A compatibility note on the abuse of Windows window class extra bytes - The Old New Thing The evolution of window and class extra bytes in Windows - The Old New Thing The case of the DLL that was not present in memory despite not being formally unloaded, part 2 - The Old New Thing Raymond's hot take on Hainanese chicken - The Old New Thing The case of the DLL that was not present in memory despite not being formally unloaded, part 1 - The Old New Thing Cancellation of Windows Runtime activities is asynchronous - The Old New Thing Microspeak elaborated: Isn't escrow just a release candidate by another name? - The Old New Thing In memory of the man who put red and green squiggles under words - The Old New Thing What does it mean when the bottom bit of my HMODULE is set? - The Old New Thing Why doesn't Get­Last­Input­Info() return info for the user I'm impersonating? - The Old New Thing Windows stack limit checking retrospective, follow-up - The Old New Thing Retrofitting the WM_COPY­DATA message onto Windows 3.1 - The Old New Thing The time the x86 emulator team found code so bad that they fixed it during emulation - The Old New Thing How can I schedule work on a thread pool with low latency? Understanding the rationale behind a rule when trying to circumvent it What’s the opposite of Clip­Cursor that lets me exclude the cursor from a region? The Microsoft Company Party where everybody played name tag swap Rotation revisited: Shuffling more than three blocks, and other small notes The back cover of C++: The Programming Language also raises questions not answered by the front cover Rotation revisited: Avoiding having to calculate the gcd when doing cycle decomposition Rotation revisited: Cycle decomposition in clang’s libcxx Rotation revisited: A shocking discovery about gcc’s unidirectional rotation algorithm Rotation revisited: Another unidirectional algorithm The placeholder name for the Windows 8 experience was “modern” Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 3 Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 2 If C# and JavaScript lets me await a Windows Runtime asynchronous operation more than once, why not C++/WinRT? A hypothetical redesign of System.Diagnostics.Process to avoid confusion over properties that are valid only when you are the one who called Start Why do you say that a COM STA thread must pump messages if I see sample code creating STA threads and not pumping messages? How do I use Win32 structures from the Windows Runtime? What is the history of the ERROR_ARENA_TRASHED error code? The classic TreeView control lets me sort by name or by lParam, but why not both? Just shows that nobody cares about debugging the parity flag any more The case of the Create­File­Mapping that always reported ERROR_ALREADY_EXISTS
Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 1
Raymond Chen · 2026-05-27 · via The Old New Thing

Suppose you have a coroutine method called GetThingAsync(), and you want to do the work of getting “something” only once and caching the result. Here’s the version that does no caching:

struct Widget : WidgetT<Widget>
{
    IAsyncOperation<Thing> GetThingAsync()
    {
        co_return co_await GetThingWorkerAsync();
    }

    // The business logic goes here
    IAsyncOperation<Result> GetThingWorkerAsync();
};

Now, if this code were written in C#, we could take advantage of the fact that C# projects the Windows Runtime IAsyncOperation as a Task, and Task objects support being awaited on multiple times.

async Task<Thing> GetThingAsync()
{
    lock (m_lock) {
        // First person to call GetThingAsync starts the task.
        if (m_task == null) {
            m_task = GetThingWorker();
        }
    }
    return await m_getThingTask;
}

But we don’t have that in C++/WinRT.

One customer tried to build a solution out of winrt::resume_on_signal, perhaps inspired by one of my earlier explorations of this topic.

// Don't use this code. See discussion.
struct Widget : WidgetT<Widget>
{
    winrt::Thing m_thing{ nullptr };
    winrt::IAsyncOperation<winrt::Thing> m_task{ nullptr };
    wil::unique_event m_finished{ wil::EventOptions::ManualReset }; /* initially unsignaled */
    bool m_busy{ false };
    std::mutex m_mutex;

    IAsyncOperation<winrt::Thing> GetThingAsync()
    {
        bool shouldStart;
        {
            std::lock_guard guard(m_mutex);
            if (m_thing != nullptr) {
                // Operation has finished.
                co_return m_thing;
            } else if (m_busy) {
                // Operation has started but not yet finished.
                shouldStart = false;
            } else {
                // Operation hasn't even started.
                m_busy = true;
                shouldStart = true;
            }
        }

        auto lifetime = get_strong();

        if (shouldStart) {
            auto task = GetThingWorker();
            m_finished.ResetEvent();
            task.Completed([weak = get_weak(), this](auto&&, auto&&) {
                if (auto strong = weak.get()) {
                    m_busy = false;
                    m_finished.SetEvent();
                }
            });
            m_task = std::move(task);
        }

        co_await winrt::resume_on_signal(m_finished.get());

        {
            std::lock_guard guard(m_mutex);
            if (m_thing == nullptr && m_task) {
                m_thing = m_task.GetResults();
            }
        }

        co_return m_thing;
    }

};

The idea is that the first time GetThingAsync() is called, we start the real task and then arrange to clear the busy flag and set the m_finished event when the task completes. Subsequent callers will see that the task has already started and will not start it again. And subsequent calls which occur after the task has completed will see that we already have a m_thing and return it immediately.

Everybody then waits for the m_finished event, and then whoever manages to enter the mutex first gets the result and saves it. Finally, everybody returns whatever is in m_thing, which should be the result of the task.

From the observation that they set m_busy back to false when the task completes, and they reset the m_finished event each time they start the task, I conclude that their intention was to allow multiple attempts to get the “something” if a previous attempt fails.

Okay, so let’s see what could go wrong.

For one thing, we see a data race because the completion lambda modifies m_busy outside the mutex. So we should at least protect that with a mutex.

Another problem is that this code is not exception-safe. If GetThingWorkerAsync throws an exception before returning an IAsyncOperation, then the m_busy flag is set and gets stuck there. This means that nobody else will try to start the task, and the m_task remains null, so all subsequent callers just fall through and return a null Thing, which may not be something that the callers are expecting. (I mean, this code certainly doesn’t handle the case where GetThingWorkerAsync produces a null result because it thinks that a null m_thing means that we should try again instead of “I successfully got nothing.”)

There’s also a race condition if the task completes just as somebody calls GetThingAsync:

Thread 1 Thread 2
GetThingAsync called
m_busy = true
shouldStart = true
GetThingWorkerAsync()
m_finished.ResetEvent()
task.Completed(...)
m_task = std::move(task)
co_await resume_on_signal
 
(task completes)
m_busy = false
 
  GetThing called
m_busy = true
shouldStart = true
GetThingWorkerAsync
m_finished.ResetEvent()
task.Completed(...)
m_task = std::move(task)
co_await resume_on_signal
m_finished.SetEvent()
(completion handler returns)
 
m_thing = m_task.GetResults()  

Notice that the second call to GetThingAsync happens after m_busy has been reset, but before we have signaled the event. This creates a window inside which another thread calls GetThingAsync and tries to start the task again. The second calls assignment to m_task overwrites the one from the first call, and then when the first caller tries to get the results, it gets them from the wrong task.

But really, this code is trying too hard. We’ll look at a simpler version next time.

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.