Потоки C++11 для обновления окон приложений MFC. SendMessage(), PostMessage() требуется?

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

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

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

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

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

Проблема, с которой я сталкиваюсь, похожа на проблему, описанную в разделе Могу ли я иметь несколько потоков GUI в 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() в CClassView::OnNewFolder() что, как я предполагаю, означает, что какая-то синхронизация между потоком C+11 и потоком пользовательского интерфейса блокирует поток C++11 от достижения его стороны join()?

Да есть тупик как нить ждет SendMessage() вернуться, пока обработчик сообщения ожидает join() для нити, чтобы закончить. В соответствии с функцией SendMessage Центра разработки Windows:

Отправляет указанное сообщение окну или окнам. 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 Desktop Applications - отправная точка со множеством ссылок. См. Также MFC COM, который является отправной точкой с большим количеством ссылок о MFC с COM вместе с этой статьей Введение в COM. Смотрите также MFC Макросы и Глобалы.

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

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

1 ответ

Решение

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

  • 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(); может использоваться для передачи контекста текущего потока в фоновый поток, что может быть полезно для длительной задачи, которая может быть в контексте основного потока пользовательского интерфейса, и вы хотите убедиться, что это не так
  • при отправке сообщений в окно убедитесь, что оно действительно создано и существует, во время запуска приложения приложение должно создавать окна, прежде чем вы сможете их использовать; только то, что класс 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 для стандарта языка C++ и использования сопрограмм требуется /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, я создал новый проект MFC Single Document Interface (SDI) с использованием стиля Visual Studio. После запуска приложения оно выглядит следующим образом.

снимок экрана приложения MFC в теме Visual Studio

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

Первым изменением, которое я сделал, было предоставление способа отправить сообщение 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() следующий код для помещения текстовой строки в окно вывода "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 которые отмечены синими стрелками на снимке экрана ниже. снимок экрана свойств решения Visual Studio, показывающий изменения

С помощью 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,

снимок экрана при наведении курсора мыши на зеленый цвет

Примечание № 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 включительно.

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

Я также провел эксперимент с древовидным элементом управления 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); });
Другие вопросы по тегам