经过一些实验,有一些我觉得很舒服的建议。
concurrency
任务功能比 C++11 更易于使用,std:thread
并且在与协程一起使用时更灵活,但是比 C++11 更易于使用并且也可以与协同程序一起std::async
使用std::thread
co_await
- 在使用 C++/WinRT 和 WinRT 中的类型函数时,使用的协程
co_await
看起来是一个很好的补充(请参阅C++ 协程:了解运算符 co_await以获得技术解释)concurrency
Async
concurrency::task<>
您可以使用模板作为函数的返回类型或使用来制作自己的异步函数,concurrency::create_task()
并且可以co_await
与此类任务一起使用
- 您还可以使用
co_await
with std::async()
sincestd::async()
返回std::future<>
具有 Awaitable 接口的 a(请参阅await/yield: C++ coroutines尽管它的日期为 2016 年 11 月)
- 您还可以使用
co_await
astd::future<>
的get_future()
方法提供的 a std::packaged_task<>
(另请参阅Packaged_task 和 async 之间的区别)
- 您可以使用
std::experimental::generator<type>
作为函数返回类型的生成器函数与co_yield
运算符一起在生成的系列中返回指定类型的值
- 更新 MFC UI 要求任何代码在 MFC UI 线程中运行 MFC 对象已创建,因此需要 Windows 消息与 MFC 窗口和来自其他线程的窗口类对象进行通信,或者您必须将线程上下文/关联性切换到 UI该对象的线程上下文
winrt::apartment_context
可用于捕获当前线程上下文并稍后恢复使用co_await
,可用于捕获主 UI 线程上下文以供稍后重用(请参阅使用 C++/WinRT 的并发和异步操作一文中考虑线程关联的编程)
co_await winrt::resume_background();
可用于将当前线程的上下文推送到后台线程,这对于可能在主 UI 线程上下文中的冗长任务很有用,并且您希望确保它不是
- 向窗口发送消息时,请确保窗口实际已创建并存在,在应用程序启动期间,应用程序必须创建窗口才能使用它们;仅仅因为 MFC 类存在并不意味着底层窗口已经创建
::SendMessage()
是同步的,其中发送消息并返回响应
::PostMessage()
是异步的,其中发送消息并且不返回响应
- 使用
::PostMessage()
时要小心,在接收者使用它们之前,在消息中发送的指针不会超出范围,因为通常在::PostMessage()
返回和接收消息的消息句柄之间存在延迟
- 可能最直接的方法是
ON_MESSAGE()
在消息映射中使用带有消息处理程序接口的宏afx_msg LRESULT OnMessageThing(WPARAM, LPARAM)
- 您可以在以定义的常量开头的空间中使用 Windows 消息标识符,
WM_APP
并且可以在不同的类中使用相同的标识符
- 您可以在 MFC 中轻松使用 C++/WinRT 的大部分内容,只需稍加小心,尽管我承认我只尝试了一些东西,并且有一些限制,例如根据文档不使用 XAML
- 如果您确实在 MFC 应用程序中使用 C++/WinRT,则您将应用程序限制为具有 Windows 运行时的 Windows 版本,这几乎意味着 Windows 10(这排除了将 C++/WinRT 与 Windows 7、POS Ready 7 等一起使用。 )
- 使用 C++/WinRT 需要添加编译器选项
/stdc++17
以启用 C++ 语言标准的 ISO C++17 标准,使用协程需要/await
编译器选项
这是查看资源。
Microsoft Build 2018
Effective C++/WinRT for UWP and Win32
May 06, 2018 at 3:27PM by Brent Rector, Kenny Kerr
CppCon 2017:Scott Jones 和 Kenny Kerr
C++/WinRT 和 Windows 上 C++ 的未来
2017 年 11 月 2 日发布
使用 Visual Studio 2017 社区版,我使用 Visual Studio 样式创建了一个新的 MFC 单文档界面 (SDI) 项目。应用程序启动后,如下图所示。
消息的辅助函数
我所做的第一个更改是提供一种将 Windows 消息发送到我要更新的窗格(ClassView 或 OutputWindow)的方法。由于CMainFrame
MainFrm.h 中的类具有这些窗口的 MFC 对象,如下所示:
protected: // control bar embedded members
CMFCMenuBar m_wndMenuBar;
CMFCToolBar m_wndToolBar;
CMFCStatusBar m_wndStatusBar;
CMFCToolBarImages m_UserImages;
CFileView m_wndFileView;
CClassView m_wndClassView;
COutputWnd m_wndOutput;
CPropertiesWnd m_wndProperties;
我修改了该类以提供一种向这些窗口发送消息的方法。我选择使用SendMessage()
而不是PostMessage()
消除指针超出范围的问题。该类concurrency
与SendMessage()
.
LRESULT SendMessageToFileView(UINT msgId, WPARAM wParam, LPARAM lParam) { return m_wndFileView.SendMessage(msgId, wParam, lParam); }
LRESULT SendMessageToClassView(UINT msgId, WPARAM wParam, LPARAM lParam) { return m_wndClassView.SendMessage(msgId, wParam, lParam); }
LRESULT SendMessageToOutputWnd(UINT msgId, WPARAM wParam, LPARAM lParam) { return m_wndOutput.SendMessage(msgId, wParam, lParam); }
这些是用于发送消息以更新各种 MFC 窗口的原始、裸露的基础设施。我将它们放入CMainFrame
类中,因为这是一个中心点,该AfxGetMainWnd()
函数允许我在 MFC 应用程序中的任何位置访问该类的对象。包装这些原始函数的附加类是合适的。
然后我将消息处理程序放入BEGIN_MESSAGE_MAP
和END_MESSAGE_MAP
宏中的每个类中。输出窗口更新是最简单和最简单的,如下所示:
BEGIN_MESSAGE_MAP(COutputWnd, CDockablePane)
ON_WM_CREATE()
ON_WM_SIZE()
// ADD_ON: message handler for the WM_APP message containing an index as
// to which output window to write to along with a pointer to the
// text string to write.
// this->SendMessageToOutputWnd(WM_APP, COutputWnd::OutputBuild, (LPARAM)_T("some text"));
ON_MESSAGE(WM_APP, OnAddItemsToPane)
END_MESSAGE_MAP()
消息处理程序看起来像:
// ADD_ON: message handler for the WM_APP message containing an array of the
// struct ItemToInsert above. Uses method AddItemsToPane().
LRESULT COutputWnd::OnAddItemsToPane(WPARAM wParam, LPARAM lParam)
{
switch (wParam) {
case OutputBuild:
m_wndOutputBuild.AddString((TCHAR *)lParam);
break;
case OutputDebug:
m_wndOutputDebug.AddString((TCHAR *)lParam);
break;
case OutputFind:
m_wndOutputFind.AddString((TCHAR *)lParam);
break;
}
return 0;
}
我将方法原型与此枚举一起添加到类中,以使该功能更易于使用。
enum WindowList { OutputBuild = 1, OutputDebug = 2, OutputFind = 3 };
通过上述更改,我能够在BOOL CMFCAppWinRTDoc::OnNewDocument()
以下代码中插入“新建”的消息处理程序,以将文本字符串放入“构建”输出窗口:
CMainFrame *p = dynamic_cast <CMainFrame *> (AfxGetMainWnd());
if (p) {
p->SendMessageToOutputWnd(WM_APP, COutputWnd::OutputBuild, (LPARAM)_T("this is a test from OnNewDocument()."));
}
将 C++/WinRT 与 MFC 和concurrency
为了测试这一点以及使用带有 MFC 的 C++/WinRT 进行测试,我添加了以下在应用程序启动时调用的concurrency
任务。CMainFrame::OnCreate()
此源衍生出一个任务,然后使用Syndication
C++/WinRT 的功能来获取 RSS 提要列表,并在标有“构建”的 OutputWindow 窗格中显示标题,如上面的屏幕截图所示。
concurrency::create_task([this]() {
winrt::init_apartment();
Sleep(5000);
winrt::Windows::Foundation::Uri uri(L"http://kennykerr.ca/feed");
winrt::Windows::Web::Syndication::SyndicationClient client;
winrt::Windows::Web::Syndication::SyndicationFeed feed = client.RetrieveFeedAsync(uri).get();
for (winrt::Windows::Web::Syndication::SyndicationItem item : feed.Items())
{
winrt::hstring title = item.Title().Text();
this->SendMessageToOutputWnd(WM_APP, COutputWnd::OutputBuild, (LPARAM)title.c_str()); // print a string to an output window in the output pane.
}
winrt::uninit_apartment();
});
要使用concurrency
C++/WinRT 功能,我必须在 MainFrm.c 源文件顶部附近添加几个包含文件。
// ADD_ON: include files for using the concurrency namespace.
#include <experimental\resumable>
#include <pplawait.h>
#pragma comment(lib, "windowsapp")
#include "winrt/Windows.Foundation.h"
#include "winrt/Windows.Web.Syndication.h"
此外,我必须修改解决方案的属性以指定 C++17 和一个附加的编译器选项,/await
在下面的屏幕截图中用蓝色箭头标记。
co_await
与 MFC 和 C++/WinRT 一起使用
从@IInspectable 的有用评论中,我了解了 Visual Studio 2017 和 MFC 的协程。我一直对它们很好奇,但似乎我无法想出任何可以编译而不会因使用co_await
.
然而,从@IInspectable 评论中的链接开始,我发现了一个指向此 YouTube 视频的链接,CppCon 2016:Kenny Kerr 和 James McNellis “Put Coroutines to Work with the Windows Runtime”,在 10:28 左右有一个源代码示例最后我能够想出一些可以编译和工作的东西。
我创建了以下函数,然后我用它来替换上面的源代码,concurrency::create_task()
并将 lambda 替换为对以下函数的函数调用。函数调用很简单,myTaskMain(this);
替换方法concurrency::create_task([this]() {
中的 lambda,int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
然后在OnCreate()
函数体上方添加以下源代码。
winrt::Windows::Foundation::IAsyncAction myTaskMain(CMainFrame *p)
{
winrt::Windows::Foundation::Uri uri(L"http://kennykerr.ca/feed");
winrt::Windows::Web::Syndication::SyndicationClient client;
winrt::Windows::Web::Syndication::SyndicationFeed feed = co_await client.RetrieveFeedAsync(uri);
Sleep(5000);
for (winrt::Windows::Web::Syndication::SyndicationItem item : feed.Items())
{
winrt::hstring title = item.Title().Text();
p->SendMessageToOutputWnd(WM_APP, COutputWnd::OutputBuild, (LPARAM)title.c_str()); // print a string to an output window in the output pane.
}
}
我从concurrency::create_task()
被替换的源头做了两个更改:
- 删除了
winrt::init_apartment();
并且winrt::uninit_apartment();
因为使用它们触发了异常并且删除它们似乎没有任何区别
- 移动
Sleep(5000);
到之后,co_await
因为离开它导致函数休眠 5 秒,这意味着 UI 线程休眠了 5 秒
我在调试器中发现,在myTaskMain()
调用函数时,函数立即返回,UI 线程继续运行,而协程在后台执行。UI 立即显示,然后大约五秒钟后,发生了更新类视图树和输出窗口“构建”选项卡中的 RSS 提要列表的附加操作。
注意 #1:我在其他测试中遇到的另一件事是 UI 将冻结几秒钟(菜单不起作用)。这似乎是由于Sleep(5000);
表明之后的代码co_await
正在主 UI 线程上运行。在我开始探索使用winrt::apartment_context ui_thread;
捕获主 UI 线程上下文以便随后使用co_await ui_thread;
将我的协程线程返回到主 UI 线程上下文之后,应用程序行为的这种变化开始了。
可能发生的情况client.RetrieveFeedAsync(uri)
是立即得到满足,没有延迟,可能来自缓存,因此不是将任务推送到另一个线程然后返回给调用者,co_await
而是立即返回结果并且函数myTaskMain()
能够立即继续使用当前线程哪个是主 UI 线程?
我注意到在 Visual Studio 2017 中co_await
使用的 withclient.RetrieveFeedAsync(uri)
是绿色的,而co_await
used withco_await ui_thread;
是蓝色的。将鼠标悬停在绿色上,co_await
我得到一个工具提示,表明这是不同版本的co_await
.
注意 #2:有一个 C++/WinRT 函数可以移动到后台线程上下文,winrt::resume_background()
它可以与co_await
. 如果我修改上述函数,将调用后的myTaskMain()
代码行替换为以下两行代码以将线程上下文推送到后台线程,我看不到冻结(UI 响应菜单选择)和大约 15 秒后,RSS 提要文本行将显示在输出窗口的“构建”选项卡中。Sleep(5000);
client.RetrieveFeedAsync(uri)
co_await winrt::resume_background(); // switch context to background thread
Sleep(15000);
concurrency::task<>
使用适用的滚动异步任务co_await
我很好奇的一件事是能够创建我自己的异步任务,我可以使用co_await
类似于 C++/WinRT 的异步类型函数。我花了一些时间搜索,直到最终找到这篇文章Concurrency and asynchronous operations with C++/WinRT,其中有一个名为Asychronously return a non-Windows-Runtime type的部分。
这是一个简单的演示函数,它使用 lambda 创建 aconcurrency::task<>
并返回然后使用的任务co_await
。这个特定的 lambda 正在返回一个int
,因此该函数被定义为一个返回一个的任务int
,concurrency::task<int>
concurrency::task<int> mySleepTaskAsync()
{
return concurrency::create_task([]() {
Sleep(15000);
return 5;
});
}
然后在语句中将上述函数与co_await
运算符一起使用,例如:
int jj = co_await mySleepTaskAsync();
这将导致变量jj
在等待 15 秒后具有 5 的值。
以上用于返回 a 的函数,winrt::Windows::Foundation::IAsyncAction
例如myTaskMain()
上面的函数。
如果你喜欢,你也可以直接使用带有co_await
as 的 lambda:
int jj = co_await concurrency::create_task([]() {
Sleep(15000);
return 5;
});
或者你可以有一个正常的功能,例如:
int mySleepTaskAsyncInt()
{
Sleep(15000);
return 5;
}
然后将其与co_await
using一起使用concurrency::task<>
,如下所示:
int jj = co_await concurrency::create_task(mySleepTaskAsyncInt);
std::async
使用适用的滚动异步任务co_await
虽然std::thread
不能与 一起使用co_await
,导致编译错误,但您可以使用std::async
with co_await
。原因是co_await
operator 需要的返回值的种类以及 a 的返回值和std::thread
astd::thread
的返回值的std::async
不同std::future<>
。
co_await
运算符要求它所操作的变量是 a ,std::future<>
具有get()
从线程检索结果的方法,并且是 Awaitable。
#include <future>
int mySleepTaskAsyncInt()
{
Sleep(7000);
return 5;
}
winrt::Windows::Foundation::IAsyncAction myTaskMain(CMainFrame *p)
{
auto t1 = co_await std::async (std::launch::async, mySleepTaskAsyncInt);
// do something with the variable t1
}
std::packaged_task<>
使用和std::future<>
使用滚动异步任务co_await
由于co_await
需要一个 Awaitable 对象,创建此类对象的另一种方法是创建一个任务,std::packaged_task<>
然后启动该任务,并使用该任务的get_future()
方法来获得一个std::future<>
然后可以使用的co_await
。
例如,我们可以有以下简单的函数,它将创建一个任务包,开始执行任务,然后返回一个std::future<>
. 然后我们可以使用这个函数作为co_await
操作符的目标来实现一个协程。
#include <future>
std::future<int> mySleepTaskStdFutureInt()
{
// create the task to prepare it for running.
std::packaged_task<int()> task([]() {
Sleep(7000);
return 455; // return an int value
});
// start the task running and return the future
return task(), task.get_future();
}
然后在我们的源代码中,我们将使用类似于以下的函数:
int jkjk = co_await mySleepTaskStdFutureInt();
该return
语句使用逗号运算符引入一个序列点,以便我们启动任务运行,然后调用get_future()
正在运行的任务上的方法。方法的结果get_future()
,astd::future<int>
是函数实际返回的结果。
创建的任务std::packaged_task()
必须以使用变量调用之类的函数开始。如果您不启动任务,则std::future<>
函数返回的将永远不会有变量,并且co_await
等待 Awaitable 完成并提供值的将永远不会触发。结果是你之后的源co_await
将不会被执行,因为co_await
永远不会被触发。
发电机co_yield
和std::experimental::generator<type>
在调查co_await
时,我遇到了co_yield
哪个用于返回一个值作为一组值的生成器的一部分。使用 Visual Studio 2017co_yield
需要包含头文件experimental/generator
。这是一个生成一系列整数的生成器的简单示例。
#include <experimental/generator>
std::experimental::generator<int> makeSomeInts(int kCount)
{
for (int i = 0; i < kCount; i++) {
co_yield i;
}
}
这个函数可以与 ranged for 一起使用,如下所示:
for (int kkk : makeSomeInts(10)) {
// code that uses the variable kkk which contains
// an int from the generated range 0 up to be not including 10.
}
将针对 0 到 9 的每个整数值执行上述循环。
更复杂的消息:更新 ClassView pan
我还对 ClassView 树控件进行了实验,以提供一种执行最基本操作的简单方法:创建一个初始树控件并添加到它。
在CClassView
ClassView.h 文件的类中,我添加了以下数据结构。顺便说一句,在我完成之后,我意识到这可能是放置它的错误位置,因为CFileView
该类使用相同的树结构,因此相同的方法适用于这两个窗格。无论如何,我添加了以下内容:
// ADD_ON: enumeration listing the various types of tree control icons which
// correspond to the position of a control in the tree.
// choose either classview_hc.bmp or classview.bmp for the bitmap strip that
// contains the 7 icons we are using for the images in our tree control.
// icons are standard size in height and width (15x15 pixels) in the order of:
// - main root icon
// - tree node icon which can be opened to show nodes beneath it
// - folder icon which is used to indicate a folder
// - method icon indicating a method of a class
// - locked method icon
// - member variable icon
// - locked member variable icon
enum IconList { MainRoot = 0, TreeNode = 1, FolderNode = 2, MethodNode = 3, MethodLockedNode = 4, MemberNode = 5, MemberLockedNode = 6 };
// ADD_ON: struct used to contain the necessary data for a node in the tree control.
struct ItemToInsert {
std::wstring label; // text to be displayed with the node.
int nImage; // zero based offset of the node's icon in the image, one of enum IconList above.
int nSelectedImage; // zero based offset of the node's icon in the image, one of enum IconList above.
};
我创建了一个消息处理程序,并将其添加到 ClassView.cpp 中的消息映射中
ON_MESSAGE(WM_APP, OnAddItemsToPane)
并添加了实际的消息处理程序本身以及执行实际处理的辅助函数。
// ADD_ON: function for filling in the ClassView pane using an array of the
// struct ItemToInsert above. array is terminated by an entry with
// all zeros as in { _T(""), 0, 0 }
void CClassView::AddItemsToPane(CViewTree &xwndClassView, void *xrayp)
{
if (xrayp == 0) return;
// the images are icons that are laid out in a line of icons within a single bitmap image.
// see class method OnChangeVisualStyle() for when the bitmap image is loaded and then
// divided up into sections, 0 through 6, of the single bitmap image loaded.
// see classview.bmp and classview_hc.bmp in the ResourceFiles list.
HTREEITEM hRoot = xwndClassView.GetRootItem();
HTREEITEM hClass = 0;
ItemToInsert *xray = (ItemToInsert *)xrayp;
for (int i = 0; xray[i].label.size() != 0; i++) {
switch (xray[i].nImage) {
case MainRoot:
hRoot = xwndClassView.InsertItem(xray[i].label.c_str(), xray[i].nImage, xray[i].nSelectedImage);
xwndClassView.SetItemState(hRoot, TVIS_BOLD, TVIS_BOLD);
xwndClassView.Expand(hRoot, TVE_EXPAND);
break;
case TreeNode:
hClass = xwndClassView.InsertItem(xray[i].label.c_str(), xray[i].nImage, xray[i].nSelectedImage, hRoot);
break;
case FolderNode:
hClass = xwndClassView.InsertItem(xray[i].label.c_str(), xray[i].nImage, xray[i].nSelectedImage, hRoot);
break;
case MethodNode:
case MethodLockedNode:
case MemberNode:
case MemberLockedNode:
xwndClassView.InsertItem(xray[i].label.c_str(), xray[i].nImage, xray[i].nSelectedImage, hClass);
break;
default:
break;
}
}
}
// ADD_ON: message handler for the WM_APP message containing an array of the
// struct ItemToInsert above. Uses method AddItemsToPane().
LRESULT CClassView::OnAddItemsToPane(WPARAM wParam, LPARAM lParam)
{
switch (wParam) {
case 1:
AddItemsToPane(m_wndClassView, (void *)lParam);
break;
}
return 0;
}
然后我为初始树创建了一些示例数据,然后添加了节点。
// ADD_ON: this is the content to be put into the ClassView tree pane.
// this is a tree structure.
CClassView::ItemToInsert xray2[] = {
{ _T("CFakeMainProject"), CClassView::MainRoot, CClassView::MainRoot },
{ _T("CFakeAboutDlg"), CClassView::TreeNode, CClassView::TreeNode },
{ _T("CFakeAboutDlg()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("CFakeApp"), CClassView::TreeNode, CClassView::TreeNode },
{ _T("CFakeApp()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("InitInstance()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("OnAppAbout()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("CFakeAppDoc"), CClassView::TreeNode, CClassView::TreeNode },
{ _T("CFakeAppDoc()"), CClassView::MethodLockedNode, CClassView::MethodLockedNode },
{ _T("~CFakeAppDoc()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("OnNewDocument()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("CFakeAppView"), CClassView::TreeNode, CClassView::TreeNode },
{ _T("CFakeAppView()"), CClassView::MethodLockedNode, CClassView::MethodLockedNode },
{ _T("~CFakeAppView()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("GetDocument()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("CFakeAppFrame"), CClassView::TreeNode, CClassView::TreeNode },
{ _T("CFakeAppFrame()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("~CFakeAppFrame()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T("m_wndMenuBar"), CClassView::MemberLockedNode, CClassView::MemberLockedNode },
{ _T("m_wndToolBar"), CClassView::MemberLockedNode, CClassView::MemberLockedNode },
{ _T("m_wndStatusBar"), CClassView::MemberLockedNode, CClassView::MemberLockedNode },
{ _T("Globals"), CClassView::FolderNode, CClassView::FolderNode },
{ _T("theFakeApp"), CClassView::MemberNode, CClassView::MemberNode },
{ _T(""), 0, 0 }
};
CClassView::ItemToInsert xray3[] = {
{ _T("CAdditionalDelay"), CClassView::TreeNode, CClassView::TreeNode },
{ _T("CAdditionalDelayMethod()"), CClassView::MethodNode, CClassView::MethodNode },
{ _T(""), 0, 0 }
};
然后,我通过在方法中执行两个concurrency
任务来执行此消息处理程序,这些任务执行CMainFrame::OnCreate()
了时间延迟,然后更新了 ClassView 窗口树的内容。
concurrency::create_task([this]() { Sleep(5000); this->SendMessageToClassView(WM_APP, 1, (LPARAM)xray2); });
concurrency::create_task([this]() { Sleep(10000); this->SendMessageToClassView(WM_APP, 1, (LPARAM)xray3); });