Потоки 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++11std: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. После запуска приложения оно выглядит следующим образом.
Вспомогательные функции для сообщений
Первым изменением, которое я сделал, было предоставление способа отправить сообщение 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
которые отмечены синими стрелками на снимке экрана ниже.
С помощью 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); });