用于更新 MFC 应用程序窗口的 C++11 线程。需要 SendMessage()、PostMessage() 吗?

Posted

技术标签:

【中文标题】用于更新 MFC 应用程序窗口的 C++11 线程。需要 SendMessage()、PostMessage() 吗?【英文标题】:C++11 threads to update MFC application windows. SendMessage(), PostMessage() required? 【发布时间】:2018-11-13 02:40:59 【问题描述】:

在使用 C++/CX 和 ++/WinRT 的简单 UWP 应用程序花费了一些时间后,我开始享受一些针对 Windows UI 应用程序开发的环境的功能。

现在不得不回到更熟悉的 MFC 应用程序开发,我想将我的方法更改为类似于 UWP 应用程序开发的方法。这个想法是使用异步 C++11 线程来生成内容并修改在 MFC UI 中显示的内容。

我想要做的主要改变是使用 C++11 线程来卸载一些耗时的任务,并让这些线程将结果传达回主 MFC UI。

我希望卸载到 C++11 线程上的一些任务,类似于我在 UWP 应用中使用 C++/CX 和 C++/WinRT 的异步任务:

连接到另一台计算机并与之交换数据 打开一个数据文件并解析它以更新 UI 视图 将数据文件转换为另一种格式,例如 CSV 并导出到文件 读取 CSV 等格式的文件并将内容转换为数据文件 在 UI 中执行搜索和过滤数据文件的呈现方式

我遇到的问题类似于Can I have multiple GUI threads in MFC? 中描述的问题,但是,我正在寻找一种通用方法,而不是该问题中的具体进度条更新。

我一直在尝试使用 Visual Studio 模板对实验性 MFC 应用程序进行简单测试,该模板在左侧停靠有一个树控件,用于在工作线程中构建树。

如果我有一个 CViewTree,一个显示树视图的 MFC 窗口,我想从 C++11 线程更新它,我目前正在使用 ::PostMessage() 请求停靠窗格中的树控件已更新。

如果我使用带有全局 std::thread 的 lambda,例如以下代码:

std::thread t1;

void CClassView::FillClassView()

    // ::SendMessage() seems to deadlock so ::PostMessage() is required.
    t1 = std::thread([this]()  Sleep(5000);  ::PostMessage(this->m_hWnd, WM_COMMAND, ID_NEW_FOLDER, 0); );


MFC 停靠窗格的消息处理程序如下所示:

void CClassView::OnNewFolder()

    t1.join();   // this join seems to deadlock if ::SendMessage() is used.

    AddItemsToPane(m_wndClassView);

确实使用树控件内容更新 MFC 停靠窗格,就像在创建 C++11 线程的同一位置调用函数 AddItemsToPane(m_wndClassView); 一样。当 C++11 线程仅用于提供线程方法实际工作的可见指示时,窗格更新会延迟 5 秒。

我的问题是我希望 C++11 线程为树控件创建内容并将其提供给停靠窗格,而不是让停靠窗格生成内容。

到目前为止,我能想到的唯一方法是开发自己的类库,该类库将提供与 MFC 库类似的 C++11 线程,并使用 ::PostMessage() 控制将适当的 Windows 消息发送到指定的 MFC 窗口或控制。

我想知道是否可以让 C++11 线程拥有自己的阴影 MFC 控件,它们会更新这些控件,然后向 UI 发送一条消息,要求 UI 使用阴影的内容更新其显示的控件MFC控制?还是人们正在使用其他一些方法?

我正在寻找其他一些不那么费力的方法来解决从 C++11 线程更新 MFC UI 的问题。

顺便说一下#1 ::SendMessage() 似乎在CClassView::OnNewFolder() 中的join() 上死锁了,我认为这意味着C+11 线程和UI 线程之间的某种同步是阻止 C++11 线程到达 join() 的一侧?

是的,存在死锁,因为线程等待SendMessage() 返回,而消息处理程序在join() 等待线程完成。根据Windows Dev Center SendMessage function:

将指定的消息发送到一个或多个窗口。 SendMessage 函数调用指定窗口的窗口过程,会 直到窗口过程处理完消息才返回

要发送消息并立即返回,请使用SendMessageCallbackSendNotifyMessage 函数。将消息发布到线程的消息 排队并立即返回,使用PostMessagePostThreadMessage 功能。

顺便说一下#2 似乎在 C++11 线程的 lambda 中使用实际的 Window 句柄而不是 this 指针会更安全。以防万一this 指针由于某些原因(例如控件被删除)变得未定义?

顺便说一下 #3 通过 #include <ppltasks.h> 提供的 Microsoft concurrency 命名空间是 C++11 线程的替代方案。 concurrency 命名空间函数的抽象级别比 C++11 线程更高,并且更易于使用。

例如上面对std:thread的使用可以重写为:

void CClassView::FillClassView()

    concurrency::create_task([this]()  Sleep(5000);  ::SendMessage(this->m_hWnd, WM_COMMAND, ID_NEW_FOLDER, 0); );

这不需要使用std::thread join() 来干净地终止线程。此外,SendMessage()PostMessage() 可用于发送 Windows 消息,因为我们没有与 C++11 线程相同的死锁问题。

备注

注意 #1: About Messages and Message Queues 和 Using Messages and Message Queues。

有关 MFC 的特定内容,请参阅Messages and Commands in the Framework。

注意 #2: Multithreading with C++ and MFC,特别是 Multithreading: Programming Tips,上面写着。

如果您有一个以某种方式创建线程的多线程应用程序 除了使用 CWinThread 对象,您不能访问其他 MFC 来自该线程的对象。换句话说,如果您想访问任何 来自辅助线程的 MFC 对象,您必须使用 多线程中描述的方法之一:创建 用户界面线程或多线程:创建工作线程。 这些方法是唯一允许类库 初始化处理多线程所需的内部变量 应用程序。

注意 #3: UWP APIs callable from a classic desktop app 上面写着:

除了一些值得注意的例外,一般规则是通用 可以从经典桌面应用程序调用 Windows 平台 (UWP) API。 API 的两个主要领域是此一般规则的例外 是 XAML UI API,以及要求调用应用具有 包标识。 UWP 应用具有定义明确的应用模型,并且它们 有一个包标识。经典桌面应用程序没有 定义明确的应用模型,并且它们没有包标识。一种 已转换为 UWP 应用的经典桌面应用确实具有 包标识。

另请参阅 2012 年 9 月关于 WinRT 与 VS 2012 和 Windows 8 的以下博客。虽然 C++/WinRT 与 VS 2017 似乎比使用的 Windows 运行时模板库 (WRL) 更适合 Windows 10:

Accessing WinRT From Desktop apps (Part 1) Accessing WinRT from Desktop Apps (Part 2)

注意 #4: MFC Desktop Applications 这是一个有很多链接的起点。另请参阅MFC COM,这是一个起点,其中包含许多关于 MFC 与 COM 的链接以及本文Introduction to COM。另见MFC Macros and Globals。

至于使用AfxGetMainWnd()获取主应用窗口,微软开发者中心在文章AfxGetMainWnd中有这样的说法:

如果从应用程序的主线程调用 AfxGetMainWnd,它 根据上述规则返回应用程序的主窗口。如果 该函数是从应用程序中的辅助线程调用的, 函数返回与创建线程关联的主窗口 来电。

【问题讨论】:

那么,为什么您使用 C++/WinRT 或 C++20 协程?这听起来像是您正在尝试实施,已经存在的东西。 C++/WinRT 具有允许您从任何给定协程继续在任何特定线程(如您的 GUI 线程)上运行代码的工具。 @IInspectable 我正在努力使用 C++/WinRT,但它似乎需要 Windows 10,除非我遗漏了什么。我有一个项目要快速完成,因此现在学习相当广泛的 C++/WinRT 视角和工具包的时间似乎令人生畏。我拥有可以通过采用 MFC 方法重用的材料,从而减少了开发工作量。从旧资源文件转到 XAML 看起来很费力。我不想重新发明 MFC 中的 C++/WinRT ***,但确实想使用其中的一些课程。有什么建议吗? @IInspectable 我现在考虑的是对大多数事情使用更传统的同步 MFC 方法,并将 C++11 线程用于特定的、非 UI 相关的行为和任务。半杯比尝试一整杯失败要好。我需要做的大部分事情都不会太雄心勃勃。 UWP 基于 Windows 运行时,一种基于 COM 的类型系统。大部分 API 表面可用于任何程序,即带有 DualApiPartitionAttribute 的程序。 C++/WinRT 作为仅标头库可以放入几乎任何项目中。 MSDN 中有 Concurrency and asynchronous operations with C++/WinRT 上的文档,展示了如何使用协程。您必须使用 /await 编译器选项。 那些文章仍然有效,尽管它们是在当时只有 C++/CX 和 C++(没有适当的投影)时发布的。即使使用WRL,代码仍然非常冗长。 C++/WinRT 提供了一个真实的投影,旨在替代 C++/CX。虽然您可以使用 Windows 运行时类型,但我建议使用 C++/WinRT 的唯一原因是它是唯一的官方第一方库,它提供帮助程序来控制协程的执行线程。 【参考方案1】:

经过一些实验,有一些我觉得很舒服的建议。

concurrency 任务功能比 C++11 std:thread 更易于使用,并且在与协程一起使用时更灵活,但是 std::asyncstd::thread 更易于使用,并且也可以与 co_await 一起使用 在使用 C++/WinRT 和 WinRT 中的 Async 类型函数时,使用 co_await 的协程看起来是对 concurrency 的一个很好的补充(有关技术说明,请参阅 C++ Coroutines: Understanding operator co_await) 您可以使用concurrency::task<> 模板作为函数的返回类型来创建自己的异步函数,或者使用concurrency::create_task(),并且您可以将co_await 用于此类任务 您也可以将co_awaitstd::async() 一起使用,因为std::async() 返回一个具有Awaitable 接口的std::future<>(请参阅await/yield: C++ coroutines,尽管它的日期为2016 年11 月) 您还可以将co_awaitstd::future<> 一起使用,由std::packaged_task<>get_future() 方法提供(另请参见What is the difference between packaged_task and async) 您可以使用std::experimental::generator<type> 作为函数返回类型以及co_yield 运算符来生成生成器函数,以在生成的系列中返回指定类型的值 要更新 MFC UI 要求任何代码在 MFC UI 线程中运行 MFC 对象已创建,因此需要 Windows 消息与 MFC 窗口和来自其他线程的窗口类对象进行通信,否则您必须切换线程上下文/关联到该对象的 UI 线程上下文 winrt::apartment_context 可用于捕获当前线程上下文并稍后使用 co_await 恢复,这可用于捕获主 UI 线程上下文以供稍后重用(请参阅文章 Concurrency and asynchronous operations with C++/WinRT 中的 Programming with thread affinity in mind ) co_await winrt::resume_background(); 可用于将当前线程的上下文推送到后台线程,这对于可能在主 UI 线程上下文中的冗长任务很有用,并且您要确保它不是 在向窗口发送消息时,请确保该窗口实际已创建并存在,在应用程序启动期间应用程序必须创建窗口才能使用它们; 仅仅因为 MFC 类存在并不意味着底层窗口已经创建 ::SendMessage() 是同步发送消息并返回响应 ::PostMessage() 是异步发送消息,不返回响应 使用::PostMessage()要小心,在接收者使用它们之前,在消息中发送的指针不会超出范围,因为在::PostMessage()返回和接收消息的消息句柄实际上对消息执行某些操作之间通常存在延迟 可能最直接的方法是在消息映射中使用ON_MESSAGE() 宏和afx_msg LRESULT OnMessageThing(WPARAM, LPARAM) 的消息处理程序接口 您可以在以WM_APP 定义的常量开头的空间中使用Windows 消息标识符,并且可以在不同的类中使用相同的标识符 您可以通过 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 2018 年 5 月 6 日下午 3:27,布伦特校长、肯尼·克尔

CppCon 2017:斯科特·琼斯和肯尼·克尔 C++/WinRT and the Future of C++ on Windows 发布于 2017 年 11 月 2 日

使用 Visual Studio 2017 社区版,我使用 Visual Studio 样式创建了一个新的 MFC 单文档界面 (SDI) 项目。应用上来后,如下图所示。

消息的帮助函数

我所做的第一个更改是提供一种将 Windows 消息发送到我要更新的窗格(ClassView 或 OutputWindow)的方法。由于 MainFrm.h 中的 CMainFrame 类具有这些窗口的 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_MAPEND_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一起使用

为了测试这一点以及使用 C++/WinRT 和 MFC 进行测试,我将以下 concurrency 任务添加到了在应用程序启动时调用的 CMainFrame::OnCreate()。此源衍生出一个任务,然后使用 C++/WinRT 的 Syndication 功能来获取 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 的附加编译器选项,在下面的屏幕截图中用蓝色箭头标记。

在 MFC 和 C++/WinRT 中使用 co_await

从@IInspectable 的有用评论中,我了解了 Visual Studio 2017 和 MFC 的协同程序。我一直对它们很好奇,但似乎我无法想出任何可以在使用 co_await 时编译时不会出错的东西。

但是,从@IInspectable 的评论中的链接开始,我发现了一个指向此 YouTube 视频的链接,CppCon 2016: Kenny Kerr & James McNellis “Putting Coroutines to Work with the Windows Runtime",它在 10:28 左右有一个源代码示例,最后我能够想出一些可以编译并工作。

我创建了以下函数,然后我用concurrency::create_task() 替换了上面的源代码,并将 lambda 替换为对以下函数的函数调用。函数调用很简单,myTaskMain(this); 替换了int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 方法中的concurrency::create_task([this]() lambda,然后在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 立即显示,然后在大约 5 秒后发生了其他操作,更新了类视图树和输出窗口的“构建”选项卡中的 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 中,与 client.RetrieveFeedAsync(uri) 一起使用的 co_await 为绿色,而与 co_await ui_thread; 一起使用的 co_await 为蓝色。将鼠标悬停在绿色 co_await 上,我得到一个工具提示,表明这是 co_await 的不同版本。

注意 #2: 有一个 C++/WinRT 函数可以移动到后台线程上下文 winrt::resume_background(),它可以与 co_await 一起使用。如果我修改上述函数myTaskMain(),将调用client.RetrieveFeedAsync(uri)后的Sleep(5000);的代码行替换为以下两行代码以将线程上下文推送到后台线程,我看不到冻结( UI 响应菜单选择)并且 RSS 提要文本行在大约 15 秒后显示在输出窗口的“构建”选项卡中。

co_await winrt::resume_background();  // switch context to background thread

Sleep(15000);

使用 concurrency::task&lt;&gt; 滚动异步任务,该任务与 co_await 一起使用

我很好奇的一件事是能够创建我自己的异步任务,我可以将其与 co_await 一起使用,类似于 C++/WinRT 的异步类型函数。我花了一些时间搜索,直到我终于找到了这篇文章,Concurrency and asynchronous operations with C++/WinRT,其中有一个名为 Asychronously return a non-Windows-Runtime type 的部分。

这是一个简单的演示函数,它创建一个带有 lambda 的 concurrency::task&lt;&gt; 并返回随后与 co_await 一起使用的任务。这个特定的 lambda 返回一个 int,因此该函数被定义为一个返回 intconcurrency::task&lt;int&gt; 的任务

concurrency::task<int> mySleepTaskAsync()

    return concurrency::create_task([]() 
        Sleep(15000);
        return 5;
    );

然后在语句中将上述函数与co_await 运算符一起使用,例如:

int jj = co_await mySleepTaskAsync();

这将导致变量 jj 在等待 15 秒后具有值 5。

上面用于返回winrt::Windows::Foundation::IAsyncAction的函数中,例如上面的函数myTaskMain()

如果您愿意,也可以直接使用带有 co_await 的 lambda,如下所示:

int jj = co_await concurrency::create_task([]() 
    Sleep(15000);
    return 5;
);

或者你可以有一个正常的功能,例如:

int mySleepTaskAsyncInt()

        Sleep(15000);
        return 5;

然后将其与co_await 一起使用,使用concurrency::task&lt;&gt;,如下所示:

int jj = co_await concurrency::create_task(mySleepTaskAsyncInt);

使用std::async 滚动异步任务,该任务与co_await 一起使用

虽然std::thread 不能与co_await 一起使用,会导致编译错误,但您可以将std::asyncco_await 一起使用。原因是co_await运算符需要的返回值的种类,以及std::thread的返回值std::threadstd::async的返回值std::future&lt;&gt;的不同。

co_await 运算符要求它所操作的变量是 std::future&lt;&gt;,具有从线程检索结果的 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&lt;&gt;std::future&lt;&gt; 使用co_await 滚动异步任务

由于co_await需要一个Awaitable对象,另一种创建此类对象的方法是使用std::packaged_task&lt;&gt;创建一个任务,然后启动该任务并使用该任务的get_future()方法获得一个std::future&lt;&gt;然后可以与co_await 一起使用。

例如,我们可以有以下简单的函数,它将创建一个任务包,开始执行任务,然后返回一个std::future&lt;&gt;。然后我们可以使用这个函数作为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() 方法的结果,std::future&lt;int&gt; 是函数实际返回的结果。

使用std::packaged_task() 创建的任务必须使用变量调用之类的函数启动。如果您不启动任务,则函数返回的 std::future&lt;&gt; 将永远不会有变量,并且等待 Awaitable 完成并提供值的 co_await 将永远不会触发。结果是你的co_await之后的源代码不会被执行,因为co_await永远不会被触发。

带有co_yieldstd::experimental::generator&lt;type&gt; 的生成器

在调查co_await 时,我遇到了co_yield,它用于返回一个值作为一组值的生成器的一部分。在 Visual Studio 2017 中使用 co_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 树控件进行了实验,以提供一种执行最基本操作的简单方法:创建一个初始树控件并添加到它。

在 ClassView.h 文件的 CClassView 类中,我添加了以下数据结构。顺便说一句,在我完成之后,我意识到这可能是错误的放置位置,因为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 
;

然后我通过在 CMainFrame::OnCreate() 方法中分离两个 concurrency 任务来练习这个消息处理程序,这会延迟时间,然后更新 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); );

【讨论】:

这似乎是在使用 PPL。虽然仍受支持,但如果您的编译器支持,建议切换到 C++ 协程。 this C++/WinRT issue 中有详细信息。无论您选择哪个,都需要注意对象的生命周期。在 lambdas 中捕获指针/引用以及将指针/引用传递给 C++ 协程可能会在运行时以惊人的方式失败。按值传递通常是安全的。如果需要传递指针,可以考虑在运行时使用弱指针来判断存在。 @IInspectable 感谢您的评论。我已经通过使用协程和co_await 的示例更新了我的答案。您提供链接的文档有一个指向 CppCon 演讲的链接,该演讲有一个源代码示例,其中突然出现了关于使用 co_await 的信息。我发现winrt::init_apartment(); 似乎不需要新功能。你能指点我解释的地方吗?我需要进一步研究弱指针。

以上是关于用于更新 MFC 应用程序窗口的 C++11 线程。需要 SendMessage()、PostMessage() 吗?的主要内容,如果未能解决你的问题,请参考以下文章

MFC非模态添加进程控件方法一(线程方法)

向 mfc 视图发送请求/更新视图

如何从 C++/MFC 程序中获取控制台窗口? [复制]

带有线程的 MFC 应用程序中的内存泄漏

利用CWinThread实现跨线程父子MFC窗口

MFC 多线程与 delete[] , d​​bgheap.c