本主题演示如何创作和使用自己的完成源类,类似于 .NET 的?TaskCompletionSource。
下面的列表中的代码作为示例提供。 其目的是说明如何编写自己的版本。 例如,支持取消和错误传播不在此示例的范围内。
#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_value
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;
//释放指定的等待对象value
static 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");
}
?
在下一个示例中,使用简单的 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_value
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;
//释放指定的等待对象value
static 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());
}
?