Потоки C ++ 11 для обновления окон приложений MFC.SendMessage (), PostMessage () требуется? - PullRequest
0 голосов
/ 03 июня 2018

Потратив немного времени на простые приложения UWP с C ++ / CX и ++ / WinRT, я начал пользоваться некоторыми функциями ориентации на эту среду для разработки приложений Windows UI.

Теперь необходимоВернемся к более знакомой разработке приложений MFC. Я хочу изменить свой подход к чему-то похожему на разработку приложений UWP.Идея состоит в том, чтобы использовать асинхронные потоки C ++ 11 для генерации содержимого и изменения содержимого, отображаемого в пользовательском интерфейсе MFC.

Основное изменение, которое я хочу сделать, - это использовать потоки C ++ 11 для некоторой разгрузки.-потребляющие задачи, и эти потоки передают результаты обратно в основной пользовательский интерфейс MFC.

Некоторые задачи, которые я ищу для разгрузки в потоки C ++ 11, аналогичны тем, которые я использовал бы в асинхронныхзадачи с C ++ / CX и C ++ / WinRT в приложениях UWP:

  • подключение и обмен данными с другим компьютером
  • открытие файла данных и анализ его для обновления представления пользовательского интерфейса
  • преобразовать файл данных в другой формат, такой как CSV, и экспортировать в файл
  • прочитать файл в формате, например CSV, и преобразовать содержимое в файл данных
  • выполнитьпоиск и фильтрация представления файла данных в пользовательском интерфейсе

Проблема, с которой я сталкиваюсь, аналогична проблеме, описанной в Могу ли я иметь несколько потоков графического интерфейсав MFC? , однако, я ищу общий подход, а не конкретное обновление индикатора выполнения в этом вопросе.

Я пытался провести простой тест с экспериментальным приложением MFC с использованием шаблона Visual Studioу которого элемент управления деревом закреплен слева для построения дерева в рабочем потоке.

Если у меня есть CViewTree, окно MFC, которое отображает представление дерева, которое я хочу обновить с C ++11 поток, в настоящее время я использую ::PostMessage(), чтобы запросить обновление элемента управления дерева в закрепленной панели.

Если я использую лямбду с глобальным std::thread, таким как следующий код:

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 содержимым элемента управления деревом, как если бы функция AddItemsToPane(m_wndClassView); была вызвана вто же самое место, где создается поток C ++ 11.Обновление панели задерживается на 5 секунд, когда поток C ++ 11 используется только для того, чтобы обеспечить видимый признак того, что подход потока действительно работает.

Моя проблема заключается в том, что я хочу, чтобы поток C ++ 11 выполнялсоздайте контент для элемента управления деревом и предоставьте его закрепленной панели, а не создавайте закрепленную панель для создания содержимого.

Пока что единственный подход, о котором я могу подумать, - это разработка моей собственной библиотеки классов, которая будет предоставлять CАналоги потока ++ 11 для библиотеки и элементов управления MFC, использующие ::PostMessage() для отправки соответствующих сообщений Windows в указанное окно или элемент управления MFC.

Мне интересно, возможно ли иметь потоки C ++ 11имеют свой собственный теневой элемент управления MFC, который они обновляют, а затем отправляют сообщение в пользовательский интерфейс с просьбой обновить его отображаемый элемент управления содержимым теневого элемента управления MFC?Или есть какой-то другой подход, который используют люди?

Я ищу другие, менее трудоемкие подходы к решению этой проблемы обновления пользовательского интерфейса MFC из потоков C ++ 11.

Кстати, # 1 ::SendMessage() кажется тупиковым на join() in CClassView::OnNewFolder(), что, как я предполагаю, означает, что какая-то синхронизация между потоком C + 11 и потоком пользовательского интерфейса блокирует C ++11 поток достигает своей стороны join()?

Да, существует тупик, поскольку поток ожидает возврата SendMessage(), в то время как обработчик сообщений ожидает в join() завершения потока.Согласно функции Windows Dev Center SendMessage :

Отправляет указанное сообщение окну или окнам.Функция SendMessage вызывает оконную процедуру для указанного окна, а -не возвращать, пока оконная процедура не обработает сообщение .

Чтобы отправить сообщение и немедленно вернуться, используйте функцию SendMessageCallback или SendNotifyMessage.Чтобы отправить сообщение в очередь сообщений потока и сразу же вернуться, используйте функцию PostMessage или PostThreadMessage.

Кстати # 2 Может показаться, что использованиефактический дескриптор окна, а не указатель this в лямбда-потоке для потока C ++ 11 будет более безопасным.На случай, если указатель this по какой-то причине станет неопределенным, например, элемент управления будет удален?

Кстати # 3 Пространство имен Microsoft concurrency, предоставляемое через #include <ppltasks.h>,является альтернативой потокам 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: О сообщениях и очередях сообщений , а также Использование сообщений и очередей сообщений .

Для содержимого, специфичного для MFCсм. Сообщения и команды в платформе .

Примечание # 2: Многопоточность с C ++ и MFC и, в частности, Многопоточность: советы по программированию, который говорит.

Если у вас есть многопоточное приложение, которое создает поток не так, как объект CWinThread, вы не можете получить доступ к другим объектам MFC из этого потока.Другими словами, если вы хотите получить доступ к любому объекту MFC из вторичного потока, вы должны создать этот поток с помощью одного из методов, описанных в Многопоточность: создание потоков пользовательского интерфейса или многопоточность: создание рабочих потоков.Эти методы являются единственными, которые позволяют библиотеке классов инициализировать внутренние переменные, необходимые для обработки многопоточных приложений.

Примечание # 3: API-интерфейсы UWP, вызываемые из классическогонастольное приложение , которое гласит:

За некоторыми заметными исключениями, общее правило состоит в том, что API универсальной платформы Windows (UWP) можно вызывать из классического настольного приложения.Исключением из этого общего правила являются две основные области API-интерфейсов: API-интерфейсы пользовательского интерфейса XAML и API-интерфейсы, для которых вызывающему приложению требуется идентификатор пакета.Приложения UWP имеют четко определенную модель приложения и идентичность пакета.Классические настольные приложения не имеют четко определенной модели приложений и не имеют идентификатора пакета.Классическое настольное приложение, которое было преобразовано в приложение UWP, действительно имеет идентичность пакета.

См. Также следующие блоги от сентября 2012 года о WinRT с VS 2012 и Windows 8. Хотя C ++ / WinRT сVS 2017 кажется более подходящим для Windows 10, чем используемая библиотека шаблонов среды выполнения Windows (WRL):

Примечание № 4: Настольные приложения MFC , которые являются отправной точкой для множествассылки.См. Также MFC COM , который является отправной точкой со множеством ссылок о MFC с COM вместе с этой статьей Введение в COM .См. Также Макросы и глобалы MFC .

Что касается использования AfxGetMainWnd() для получения главного окна приложения, центр разработчиков Microsoft может сказать это в статье AfxGetMainWnd :

Если AfxGetMainWnd вызывается из основного потока приложения, оно возвращает главное окно приложения в соответствии с вышеуказанными правилами.Если функция вызывается из вторичного потока в приложении, функция возвращает главное окно, связанное с потоком, который сделал вызов.

1 Ответ

0 голосов
/ 08 июня 2018

После некоторых экспериментов есть несколько рекомендаций, которые мне удобно делать.

  • Функциональность задачи concurrency проще в использовании, чем C ++ 11 std:thread, и она более гибкая в использовании.использование с сопрограммами, однако, std::async проще в использовании, чем std::thread, и работает с co_await, а также
  • сопрограммы с использованием co_await выглядят как отличное дополнение к concurrency при использовании C ++ / WinRT ифункции типа Async в WinRT (см. Подпрограммы C ++: понимание оператора co_await для технического объяснения)
  • вы можете создавать свои собственные асинхронные функции, используя шаблон concurrency::task<> в качестве возвращаемого типафункции или используйте concurrency::create_task(), и вы можете использовать co_await с такой задачей
  • вы также можете использовать co_await с std::async(), поскольку std::async() возвращает std::future<>, который имеет интерфейс Awaitable(см. await / yield: сопрограммы C ++ , хотя он датирован ноябрем-2016 г.)
  • , вы также можете использовать co_await с std::future<>, как предусмотрено методом get_future()std::packaged_task<> (см. Также Что яРазница между packaged_task и async )
  • позволяет создавать функции-генераторы, используя std::experimental::generator<type> в качестве возвращаемого типа функции, а также оператор co_yield для возврата значения указанного типа в сгенерированной серии
  • для обновления пользовательского интерфейса MFC требуется, чтобы любой код выполнялся в потоке пользовательского интерфейса MFC, в котором был создан объект MFC, поэтому сообщения Windows необходимы для связи с окнами MFC и объектами класса окна из других потоков или необходимо переключить потокконтекст / сходство с контекстом потока пользовательского интерфейса для этого объекта
  • winrt::apartment_context может использоваться для захвата текущего контекста потока, а затем возобновляется с использованием co_await, который может использоваться для захвата контекста основного потока пользовательского интерфейса, который будет использоваться повторнопозже (см. Программирование с учетом сходства потоков в статье Параллельные и асинхронные операции с C ++ / WinRT)
  • co_await winrt::resume_background(); можно использовать для передачи контекста текущего потока в фоновый поток, которыйможет быть полезным для длительной задачи, которая может быть в маеn Контекст потока пользовательского интерфейса, и вы хотите убедиться, что при отправке сообщений в окно это не
  • , убедитесь, что окно действительно было создано и существует, во время запуска приложения приложение должно создать окна, прежде чем вы сможетеиспользуй их; только то, что класс MFC существует, еще не означает, что базовое окно создано.
  • ::SendMessage() синхронно отправляет сообщение и возвращает ответ
  • ::PostMessage() является асинхронным, при котором сообщение отправляется, а ответ не возвращается
  • с использованием ::PostMessage() будьте осторожны, чтобы указатели, отправленные в сообщениях, не выходили за рамки, пока их не использовал получатель, поскольку обычнозадержка между возвращением ::PostMessage() и обработчиком сообщения, получающим сообщение, на самом деле что-то делает с сообщением
  • , вероятно, самый простой подход - использовать макрос ON_MESSAGE() в карте сообщений с интерфейсом обработчика сообщений afx_msg LRESULT OnMessageThing(WPARAM, LPARAM)
  • вы можете использовать идентификаторы сообщений Windows в пространстве, начинающемся с определенной константы WM_APP, и один и тот же идентификатор можно использовать в разных классах
  • вы можете использовать большую часть C ++ / WinRTдовольно легко с MFC с небольшим вниманием, хотя по общему признанию, я только попробовал несколько вещейи существуют некоторые ограничения, такие как не использование XAML в соответствии с документацией
  • , если вы используете C ++ / WinRT в приложении MFC, вы ограничиваете свое приложение версиями Windows с Windows Runtime , что в значительной степени означает Windows 10 (это исключает использование C ++ / WinRT с Windows 7, POS Ready 7 и т. Д.)
  • с использованием C ++ / WinRT требует добавления опции компилятора /stdc++17 для включения ISO C ++17 Standard для C ++ Language Standard и использование сопрограмм требует опции компилятора /await

Вот ресурсы просмотра.

Microsoft Build 2018 Effective C ++/ WinRT для UWP и Win32 06 мая 2018 года в 15:27 от Брента Ректора, Кенни Керра

CppCon 2017: Скотт Джонс и Кенни Керр C ++ / WinRT и будущее C ++ для Windows Опубликовано 2 ноября 2017 г.

Использование Visual Studio 2017 Community Edition, Iсоздал новый проект MFC Single Document Interface (SDI), используя стиль Visual Studio.После запуска приложения оно выглядит следующим образом.

screen shot of MFC app in Visual Studio theme

Вспомогательные функции для сообщений

Первым изменением, которое я сделал, было предоставление способа отправить сообщение 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 };

С учетом вышеупомянутых изменений я смог вставить в обработчик сообщений для "New" в BOOL CMFCAppWinRTDoc::OnNewDocument() следующий код для помещения текстовой строки вОкно вывода «Build»:

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(), которая вызывается при запуске приложения.Этот источник приводит к выполнению задачи, которая затем использует функциональность Syndication C ++ / WinRT для получения списка каналов RSS и отображает заголовки на панели OutputWindow, помеченные как «Build», как показано на снимке экрана выше.

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, которые отмечены синими стрелками на снимке экрана ниже.screen shot of Visual Studio solution properties showing changes

Использование co_await с MFC и C ++ / WinRT

Из полезного комментария от @IInspectable я взглянул на сопрограммыс Visual Studio 2017 и MFC.Мне было любопытно узнать о них, однако мне показалось, что я не могу придумать ничего, что могло бы скомпилировать без ошибок при использовании co_await.

Однако, начиная со ссылки в комментарии от @IInspectable, я нашел ссылку наэто видео на YouTube, CppCon 2016: Кенни Керр и Джеймс Макнеллис «Использование сопрограмм для работы со средой выполнения Windows» , в котором был пример исходного кода во время 10:28, и, наконец, я смог что-то придуматьэто скомпилировало бы и работало.

Я создал следующую функцию, которую я затем использовал, чтобы заменить вышеупомянутый источник на concurrency::create_task(), а лямбда-функцию - вызовом следующей функции. Вызов функции был простым, myTaskMain(this); заменив concurrency::create_task([this]() { лямбду в методе 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 секунд, что означало, что поток пользовательского интерфейса спал в течение 5 секунд

Что я нашел сОтладчик состоял в том, что в тот момент, когда была вызвана функция myTaskMain(), был немедленный возврат из функции, и поток пользовательского интерфейса продолжал работать, пока сопрограмма выполнялась в фоновом режиме.Пользовательский интерфейс быстро отобразил, а затем через пять секунд произошли дополнительные действия, обновив дерево представления класса и список RSS-каналов на вкладке «Построение» в окне вывода.

Примечание № 1: Еще одна вещь, с которой я столкнулся в ходе другого тестирования, заключается в том, что пользовательский интерфейс будет зависать на несколько секунд (меню не работает).Похоже, это связано с тем, что Sleep(5000); указывает на то, что код после co_await выполняется в основном потоке пользовательского интерфейса.Это изменение в поведении приложения началось после того, как я начал исследовать использование winrt::apartment_context ui_thread; для захвата контекста основного потока пользовательского интерфейса, чтобы затем использовать co_await ui_thread; для возврата моего потока сопрограммы в контекст основного потока пользовательского интерфейса.

Что можетпроисходит то, что client.RetrieveFeedAsync(uri) немедленно выполняется без задержки, возможно, из кеша, поэтому вместо того, чтобы переносить задачу в другой поток и затем возвращаться к вызывающей стороне, co_await получает немедленный результат, и функция myTaskMain() может немедленно продолжить использование текущего потока, который является основным потоком пользовательского интерфейса?

Я заметил, что в Visual Studio 2017 co_await, используемый с client.RetrieveFeedAsync(uri), окрашен в зеленый цвет, в то время как co_await используется сco_await ui_thread; синий.При наведении курсора мыши на зеленый цвет co_await я получаю подсказку, указывающую, что это другая версия co_await.

screen shot of mouse hover over green co_await showing operator overload

Примечание # 2: Существует функция C ++ / WinRT для перемещения в контекст фонового потока, winrt::resume_background(), которую можно использовать с co_await.Если я изменю вышеуказанную функцию myTaskMain(), заменив строку кода Sleep(5000); после вызова client.RetrieveFeedAsync(uri) следующими двумя строками кода, чтобы перенести контекст потока в фоновый поток, я не вижу зависания (пользовательский интерфейс реагирует на выбранные пункты меню), а текстовые строки RSS-канала отображаются на вкладке «Построение» в окне «Вывод» через 15 секунд.

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

Sleep(15000);

Выполнение асинхронной задачи с использованием concurrency::task<> который работает с co_await

Одна вещь, которая меня интересовала, была возможность создания моей собственной асинхронной задачи, которую я мог бы использовать с co_await, аналогично функциям асинхронного типа в C ++ / WinRT.Я потратил некоторое время на поиски, пока, наконец, не нашел эту статью, Параллелизм и асинхронные операции с C ++ / WinRT , с разделом под названием Асинхронно возвращает тип не-Windows-Runtime .

Вот простая демонстрационная функция, которая создает concurrency::task<> с лямбда-выражением и возвращает задание, которое затем используется с co_await.Эта конкретная лямбда возвращает 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 будет иметь значение 5 после ожидания 15 секунд.

Вышеупомянутое используется в функции, которая возвращаетwinrt::Windows::Foundation::IAsyncAction, например, функция myTaskMain() выше.

Если хотите, вы также можете просто напрямую использовать лямбду с co_await, как в:

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

Или вы можете иметьобычная функция, такая как:

int mySleepTaskAsyncInt()
{
        Sleep(15000);
        return 5;
}

, а затем использовать ее с co_await, используя concurrency::task<>, как в:

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

Выполнение асинхронной задачи с использованием std::async работает с co_await

Хотя std::thread не работает с co_await, что приводит к ошибке компиляции, вы можете использовать std::async с co_await.Причиной является тип возвращаемого значения, который требуется оператору co_await, и разница в возвращаемом значении std::thread, std::thread и возвращаемом значении std::async, std::future<>.

Оператор co_await требует, чтобы переменная, с которой он работает, была std::future<>, имела метод get() для получения результата из потока и была ожидаемой.

#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() метода, std::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 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;
    }
}

И эту функцию можно использовать с ранжированием для, как в:

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. Включая 9

Более сложное сообщение: обновление панорамы ClassView

Я также провел эксперимент с элементом управления ClassView Tree, чтобы обеспечитьпростой способ выполнения самых элементарных действий: создать начальный элемент управления деревом и добавить к нему.

В 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); });
...