本主题演示如何创作和使用自己的完成源类,类似于 .NET 的 TaskCompletionSource。
completion_source 示例的源代码
下面的列表中的代码作为示例提供。 其目的是说明如何编写自己的版本。 例如,支持取消和错误传播不在此示例的范围内。
#include <winrt/base.h>
#include <windows.h>template <typename T>
struct completion_source
{completion_source(){m_signal.attach(::CreateEvent(nullptr, true, false, nullptr));}void set(T const& value){m_value = value;::SetEvent(m_signal.get());}bool await_ready() const noexcept{return ::WaitForSingleObject(m_signal.get(), 0) == 0;}void await_suspend(std::experimental::coroutine_handle<> resume){m_wait.attach(winrt::check_pointer(::CreateThreadpoolWait(callback, resume.address(), nullptr)));::SetThreadpoolWait(m_wait.get(), m_signal.get(), nullptr);}T await_resume() const noexcept{return m_value;}private:static void __stdcall callback(PTP_CALLBACK_INSTANCE, void* context, PTP_WAIT, TP_WAIT_RESULT) noexcept{std::experimental::coroutine_handle<>::from_address(context)();}struct wait_traits{using type = PTP_WAIT;static void close(type value) noexcept{::CloseThreadpoolWait(value);}static constexpr type invalid() noexcept{return nullptr;}};winrt::handle m_signal;winrt::handle_type<wait_traits> m_wait;T m_value{};
};
将完成卸载到单独的协同程序
本部分演示 completion_source 的一个用例。 在 Visual Studio 中创建一个基于 Windows 控制台应用程序 (C++/WinRT) 项目模板的新项目,然后将以下代码清单粘贴到 main.cpp
(根据上一节中的列表展开 completion_source 的定义)。
// main.cpp
#include "pch.h"#include <winrt/base.h>
#include <windows.h>template <typename T>
struct completion_source
{//构造函数completion_source(){m_signal.attach(::CreateEvent(nullptr, true, false, nullptr));}void set(T const& value){m_value = value;::SetEvent(m_signal.get());//将指定的事件对象设置为信号状态。}//准备bool await_ready() const noexcept{return ::WaitForSingleObject(m_signal.get(), 0) == 0;//等待指定的对象处于信号状态或超时间隔已过。若要进入可警报等待状态,请使用 WaitForSingleObjectEx 函数。 若要等待多个对象,请使用 WaitForMultipleObjects。}//暂停void await_suspend(std::experimental::coroutine_handle<> resume){//CreateThreadpoolWait:创建新的等待对象。//参数1:[in] pfnwa 等待完成或超时时要调用的回调函数。//参数2:[in, out, optional] pv 要传递给回调函数的可选应用程序定义数据。//参数3:[in, optional] pcbe 定义执行回调的环境 的TP_CALLBACK_ENVIRON 结构。 InitializeThreadpoolEnvironment 函数返回此结构。如果此参数为 NULL,则回调在默认回调环境中执行。 有关详细信息,请参阅 InitializeThreadpoolEnvironment。m_wait.attach(winrt::check_pointer(::CreateThreadpoolWait(callback, resume.address(), nullptr)));::SetThreadpoolWait(m_wait.get(), m_signal.get(), nullptr);//设置 wait 对象,替换上一个等待对象(如果有)。 工作线程在句柄发出信号后或在指定的超时过期后调用 wait 对象的回调函数。}//返回m_valueT await_resume() const noexcept{return m_value;}private://回调函数static void __stdcall callback(PTP_CALLBACK_INSTANCE, void* context, PTP_WAIT, TP_WAIT_RESULT) noexcept{std::experimental::coroutine_handle<>::from_address(context)();}struct wait_traits{using type = PTP_WAIT;//释放指定的等待对象valuestatic void close(type value) noexcept //noexcept:指定某个函数是否可能会引发异常{::CloseThreadpoolWait(value);//释放指定的等待对象。}//返回空指针static constexpr type invalid() noexcept //constexpr:它表示 constant(常数)表达式。 与 const 一样,它可以应用于变量:如果任何代码试图 modify(修改)该值,将引发编译器错误。{return nullptr; }};winrt::handle m_signal; //会话句柄winrt::handle_type<wait_traits> m_wait;T m_value{};
};using namespace winrt;
using namespace Windows::Foundation;
using namespace std::literals;fire_and_forget CompleteAfterFiveSecondsAsync(completion_source<bool>& completionSource)
{co_await 5s;completionSource.set(true);
}IAsyncAction CompletionSourceExample1Async()
{completion_source<bool> completionSource;CompleteAfterFiveSecondsAsync(completionSource);co_await completionSource;
}int main()
{auto asyncAction{ CompletionSourceExample1Async() };puts("waiting");asyncAction.get();puts("done");
}
将 completion_source 封装在类中,并返回一个值
在下一个示例中,使用简单的 App 类封装 completion_source,并在完成时返回值。 在 Visual Studio 中创建一个基于 Windows 控制台应用程序 (C++/WinRT) 项目模板的新项目,然后将以下代码清单粘贴到 main.cpp
(根据上一节中的列表展开 completion_source 的定义)。
// main.cpp
#include "pch.h"#include <winrt/base.h>
#include <windows.h>template <typename T>
struct completion_source
{//构造函数completion_source(){m_signal.attach(::CreateEvent(nullptr, true, false, nullptr));}void set(T const& value){m_value = value;::SetEvent(m_signal.get());//将指定的事件对象设置为信号状态。}//准备bool await_ready() const noexcept{return ::WaitForSingleObject(m_signal.get(), 0) == 0;//等待指定的对象处于信号状态或超时间隔已过。若要进入可警报等待状态,请使用 WaitForSingleObjectEx 函数。 若要等待多个对象,请使用 WaitForMultipleObjects。}//暂停void await_suspend(std::experimental::coroutine_handle<> resume){//CreateThreadpoolWait:创建新的等待对象。//参数1:[in] pfnwa 等待完成或超时时要调用的回调函数。//参数2:[in, out, optional] pv 要传递给回调函数的可选应用程序定义数据。//参数3:[in, optional] pcbe 定义执行回调的环境 的TP_CALLBACK_ENVIRON 结构。 InitializeThreadpoolEnvironment 函数返回此结构。如果此参数为 NULL,则回调在默认回调环境中执行。 有关详细信息,请参阅 InitializeThreadpoolEnvironment。m_wait.attach(winrt::check_pointer(::CreateThreadpoolWait(callback, resume.address(), nullptr)));::SetThreadpoolWait(m_wait.get(), m_signal.get(), nullptr);//设置 wait 对象,替换上一个等待对象(如果有)。 工作线程在句柄发出信号后或在指定的超时过期后调用 wait 对象的回调函数。}//返回m_valueT await_resume() const noexcept{return m_value;}private://回调函数static void __stdcall callback(PTP_CALLBACK_INSTANCE, void* context, PTP_WAIT, TP_WAIT_RESULT) noexcept{std::experimental::coroutine_handle<>::from_address(context)();}struct wait_traits{using type = PTP_WAIT;//释放指定的等待对象valuestatic void close(type value) noexcept //noexcept:指定某个函数是否可能会引发异常{::CloseThreadpoolWait(value);//释放指定的等待对象。}//返回空指针static constexpr type invalid() noexcept //constexpr:它表示 constant(常数)表达式。 与 const 一样,它可以应用于变量:如果任何代码试图 modify(修改)该值,将引发编译器错误。{return nullptr; }};winrt::handle m_signal; //会话句柄winrt::handle_type<wait_traits> m_wait;T m_value{};
};using namespace winrt;
using namespace Windows::Foundation;
using namespace std::literals;struct App
{completion_source<winrt::hstring> m_completionSource;IAsyncOperation<winrt::hstring> CompletionSourceExample2Async(){co_return co_await m_completionSource;}winrt::fire_and_forget CompleteAfterFiveSecondsAsync(){co_await 5s;m_completionSource.set(L"Hello, World!");}
};int main()
{App app;auto asyncAction{ app.CompletionSourceExample2Async() };app.CompleteAfterFiveSecondsAsync();puts("waiting");auto message = asyncAction.get();printf("%ls\n", message.c_str());
}