Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Toto téma popisuje pokročilé scénáře souběžnosti a asynchronní synchronizace v C++/WinRT.
Úvod do tohoto předmětu si nejprve přečtěte Souběžnost a asynchronní operace.
Přesouvání práce do fondu vláken Windows
Korutina je funkce jako každá jiná v tom, že volající je zablokován, dokud funkce nevrátí provádění zpět k němu. První příležitostí pro návrat korutiny je co_await, co_returnnebo co_yield.
Než tedy v korutině provedete práci náročnou na výpočty, musíte vrátit řízení volajícímu (jinými slovy, zavést bod pozastavení), aby volající nebyl zablokovaný. Pokud už neděláte něco jiného prováděním operace co_await, můžete použít funkci co_awaitwinrt::resume_background. Vrátí řízení volajícímu a okamžitě obnoví provádění ve vlákně fondu vláken.
V implementaci je používán nízkoúrovňový vláknový fond Windows , což zajišťuje jeho optimální efektivitu.
IAsyncOperation<uint32_t> DoWorkOnThreadPoolAsync()
{
co_await winrt::resume_background(); // Return control; resume on thread pool.
uint32_t result;
for (uint32_t y = 0; y < height; ++y)
for (uint32_t x = 0; x < width; ++x)
{
// Do compute-bound work here.
}
co_return result;
}
Programování s ohledem na přidružení vláken
Tento scénář se rozšiřuje o předchozí scénář. Přesouváte některé úlohy do poolu vláken, a pak chcete zobrazit průběh v uživatelském rozhraní (UI).
IAsyncAction DoWorkAsync(TextBlock textblock)
{
co_await winrt::resume_background();
// Do compute-bound work here.
textblock.Text(L"Done!"); // Error: TextBlock has thread affinity.
}
Výše uvedený kód vyvolá výjimku winrt::hresult_wrong_thread, protože TextBlock musí být aktualizován z vlákna, které ho vytvořilo, což je vlákno uživatelského rozhraní. Jedním z řešení je zachytit kontext vlákna, ve kterém byla naše korutina původně volána. Uděláte to tak, že vytvoříte instanci objektu winrt::apartment_context, provedete práci na pozadí a pak co_awaitapartment_context přepnout zpět do kontextu volání.
IAsyncAction DoWorkAsync(TextBlock textblock)
{
winrt::apartment_context ui_thread; // Capture calling context.
co_await winrt::resume_background();
// Do compute-bound work here.
co_await ui_thread; // Switch back to calling context.
textblock.Text(L"Done!"); // Ok if we really were called from the UI thread.
}
Pokud je výše uvedená korutina volána z vlákna uživatelského rozhraní, které vytvořilo TextBlock, pak tato technika funguje. V aplikaci bude mnoho případů, kdy jste si jistí.
Obecnější řešení aktualizace uživatelského rozhraní, které se zabývá případy, kdy si nejste jisti volajícím vláknem, můžete co_await funkci winrt::resume_foreground a přepnout na konkrétní vlákno popředí. Ve vyžkoušeném příkladu kódu určíme hlavní vlákno předáním objektu Dispatcher přidruženého k TextBlock (získáním přístupu k jeho vlastnosti Dispatcher). Implementace winrt::resume_foreground volá CoreDispatcher.RunAsync na objektu dispečera, aby spustila práci, která následuje v korutině.
IAsyncAction DoWorkAsync(TextBlock textblock)
{
co_await winrt::resume_background();
// Do compute-bound work here.
// Switch to the foreground thread associated with textblock.
co_await winrt::resume_foreground(textblock.Dispatcher());
textblock.Text(L"Done!"); // Guaranteed to work.
}
Funkce winrt::resume_foreground přebírá volitelný parametr priority. Pokud používáte tento parametr, je vhodný vzor uvedený výše. Pokud ne, můžete se rozhodnout zjednodušit co_await winrt::resume_foreground(someDispatcherObject); pouze do co_await someDispatcherObject;.
Kontexty spuštění, obnovení a přepínání v korutině
Obecně řečeno, po bodu pozastavení v korutinu může původní vykonávací vlákno zmizet a obnovení může proběhnout na jakémkoli vlákně (jinými slovy, jakékoli vlákno může volat metodu Completed pro asynchronní operaci).
Pokud ale co_await některý ze čtyř typů asynchronních operací prostředí Windows Runtime (IAsyncXxx), pak C++/WinRT zachytí kontext volání v okamžiku, kdy co_await. A zajišťuje, že jste stále v tomto kontextu, když se pokračování obnoví. C++/WinRT to dělá tak, že zkontrolujete, jestli už používáte kontext volání, a pokud ne, přepnete na něj. Pokud jste byli na vlákně podle jednovláknového modelu (STA) před co_await, budete na stejném vlákně poté; pokud jste byli na vlákně podle vícevláknového modelu (MTA) před co_await, budete na stejném vlákně poté.
IAsyncAction ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
// The thread context at this point is captured...
SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
// ...and is restored at this point.
}
Důvodem, proč se na toto chování můžete spolehnout, je to, že jazyk C++/WinRT poskytuje kód pro přizpůsobení těchto typů asynchronních operací prostředí Windows Runtime podpoře korutinového jazyka C++ (tyto části kódu se nazývají adaptéry čekání). Zbývající očekávatelné typy v C++/WinRT jsou jednoduše obálky fondu vláken a/nebo pomocné nástroje; proto se dokončují v rámci fondu vláken.
using namespace std::chrono_literals;
IAsyncOperation<int> return_123_after_5s()
{
// No matter what the thread context is at this point...
co_await 5s;
// ...we're on the thread pool at this point.
co_return 123;
}
Pokud co_await nějaký jiný typ ( i v rámci implementace coroutine C++/WinRT), pak další knihovna poskytuje adaptéry a budete muset pochopit, co tyto adaptéry dělají z hlediska obnovení a kontextu.
Pokud chcete zachovat kontextové přepínače na minimum, můžete použít některé techniky, které jsme už viděli v tomto tématu. Podívejme se na některé ilustrace, jak to udělat. V tomto dalším příkladu pseudokódu zobrazíme osnovu obslužné rutiny události, která volá rozhraní API prostředí Windows Runtime pro načtení obrázku, poklesne do vlákna na pozadí pro zpracování tohoto obrázku a pak se vrátí do vlákna uživatelského rozhraní, aby se obrázek zobrazil v uživatelském rozhraní.
IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
// We begin in the UI context.
// Call StorageFile::OpenAsync to load an image file.
// The call to OpenAsync occurred on a background thread, but C++/WinRT has restored us to the UI thread by this point.
co_await winrt::resume_background();
// We're now on a background thread.
// Process the image.
co_await winrt::resume_foreground(this->Dispatcher());
// We're back on MainPage's UI thread.
// Display the image in the UI.
}
V tomto scénáři je při volání StorageFile::OpenAsync trochu neefektivnosti. Existuje nezbytné přepnutí kontextu na vlákno na pozadí (aby obslužná rutina mohla vrátit provádění volajícímu), po jehož obnovení C++/WinRT obnoví kontext vlákna uživatelského rozhraní. V tomto případě ale není nutné být ve vlákně uživatelského rozhraní, dokud se chystáme aktualizovat uživatelské rozhraní. Čím více rozhraní API Windows Runtime voláme před voláním winrt::resume_background, tím více zbytečných kontextových přepínačů si způsobujeme. Řešení nevyvolá žádné rozhraní API prostředí Windows Runtime. Přesuňte je všechny za winrt::resume_background.
IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
// We begin in the UI context.
co_await winrt::resume_background();
// We're now on a background thread.
// Call StorageFile::OpenAsync to load an image file.
// Process the image.
co_await winrt::resume_foreground(this->Dispatcher());
// We're back on MainPage's UI thread.
// Display the image in the UI.
}
Pokud chcete udělat něco pokročilejšího, můžete napsat vlastní adaptéry await. Pokud například chcete, aby co_await pokračovalo ve stejném vlákně, na kterém se asynchronní akce dokončí (takže nedochází k přepnutí kontextu), můžete začít tím, že napíšete adaptéry await podobné těm uvedeným níže.
Poznámka:
Níže uvedený příklad kódu je poskytován pouze pro vzdělávací účely; Je potřeba začít pochopit, jak fungují adaptéry await. Pokud chcete použít tuto techniku ve vlastním základu kódu, doporučujeme vyvíjet a testovat vlastní struktury adaptérů await. Můžete například napsat complete_on_any, complete_on_current, a complete_on(dispatcher). Zvažte také vytvoření šablon, které přebírají IAsyncXxx typ jako parametr šablony.
struct no_switch
{
no_switch(Windows::Foundation::IAsyncAction const& async) : m_async(async)
{
}
bool await_ready() const
{
return m_async.Status() == Windows::Foundation::AsyncStatus::Completed;
}
void await_suspend(std::experimental::coroutine_handle<> handle) const
{
m_async.Completed([handle](Windows::Foundation::IAsyncAction const& /* asyncInfo */, Windows::Foundation::AsyncStatus const& /* asyncStatus */)
{
handle();
});
}
auto await_resume() const
{
return m_async.GetResults();
}
private:
Windows::Foundation::IAsyncAction const& m_async;
};
Abyste pochopili, jak používat adaptéry no_switch await, musíte nejdřív vědět, že když kompilátor jazyka C++ narazí na výraz co_await, hledá funkce s názvy await_ready, await_suspenda await_resume. Knihovna C++/WinRT poskytuje tyto funkce, abyste ve výchozím nastavení získali přiměřené chování, jako je tento.
IAsyncAction async{ ProcessFeedAsync() };
co_await async;
Pokud chcete použít adaptéry no_switch await, například takto: změňte typ tohoto výrazu co_await z IAsyncXxx na no_switch.
IAsyncAction async{ ProcessFeedAsync() };
co_await static_cast<no_switch>(async);
Místo hledání tří await_xxx funkcí, které odpovídají IAsyncXxx , kompilátor jazyka C++ hledá funkce, které odpovídají no_switch.
Podrobnější informace o winrt::resume_foreground
Od C++/WinRT 2.0, funkce winrt::resume_foreground pozastaví i v případě, že je volána z vlákna dispečera (v předchozích verzích mohla v některých scénářích způsobit zablokování, protože se pozastavila, pouze pokud již nebyla na vláknu dispečera).
Aktuální chování znamená, že můžete spoléhat na rozbalování zásobníku a opětovné řazení do fronty; což je důležité pro stabilitu systému, zejména u systémového kódu nízké úrovně. Poslední výpis kódu v části Programování s ohledem na přiřazení vláken, výše uvedené, ilustruje provádění některých složitých výpočtů v pozadí na vlákně a poté přepnutí na příslušné UI vlákno pro aktualizaci uživatelského rozhraní (UI).
Takto vypadá winrt::resume_foreground interně.
auto resume_foreground(...) noexcept
{
struct awaitable
{
bool await_ready() const
{
return false; // Queue without waiting.
// return m_dispatcher.HasThreadAccess(); // The C++/WinRT 1.0 implementation.
}
void await_resume() const {}
void await_suspend(coroutine_handle<> handle) const { ... }
};
return awaitable{ ... };
};
Toto současné a předchozí chování je podobné rozdílu mezi PostMessage a SendMessage při vývoji aplikací Win32. PostMessage zařadí úkol do fronty a pak uvolní zásobník bez čekání na dokončení úkolu. Odvíjení zásobníku může být nezbytné.
Funkce winrt::resume_foreground původně podporovala jen CoreDispatcher (svázaný s CoreWindow), který byl zaveden před vydáním systému Windows 10. Od té doby jsme zavedli flexibilnější a efektivnější dispečerský systém: DispatcherQueue. Můžete vytvořit DispatcherQueue pro vlastní účely. Zvažte tuto jednoduchou konzolovou aplikaci.
using namespace Windows::System;
winrt::fire_and_forget RunAsync(DispatcherQueue queue);
int main()
{
auto controller{ DispatcherQueueController::CreateOnDedicatedThread() };
RunAsync(controller.DispatcherQueue());
getchar();
}
Výše uvedený příklad vytvoří frontu (obsaženou v kontroleru) v soukromém vlákně a poté předá kontroler korutině. Korutin může pomocí fronty čekat (pozastavit a obnovit) v privátním vlákně. Dalším běžným použitím DispatcherQueue je vytvoření fronty na aktuálním vlákně uživatelského rozhraní pro tradiční desktopovou aplikaci nebo aplikaci Win32.
DispatcherQueueController CreateDispatcherQueueController()
{
DispatcherQueueOptions options
{
sizeof(DispatcherQueueOptions),
DQTYPE_THREAD_CURRENT,
DQTAT_COM_STA
};
ABI::Windows::System::IDispatcherQueueController* ptr{};
winrt::check_hresult(CreateDispatcherQueueController(options, &ptr));
return { ptr, take_ownership_from_abi };
}
To ukazuje, jak můžete volat a začlenit funkce Win32 do projektů C++/WinRT tak, že jednoduše zavoláte funkci ve stylu Win32 CreateDispatcherQueueController, která vytvoří kontroler, a poté dojde k přenesení vlastnictví výsledného kontroleru fronty k volajícímu objektu jako k objektu WinRT. Takto můžete přesně podporovat efektivní a plynulé řazení front ve vaší stávající desktopové aplikaci typu Win32 ve stylu Petzold.
winrt::fire_and_forget RunAsync(DispatcherQueue queue);
int main()
{
Window window;
auto controller{ CreateDispatcherQueueController() };
RunAsync(controller.DispatcherQueue());
MSG message;
while (GetMessage(&message, nullptr, 0, 0))
{
DispatchMessage(&message);
}
}
Nahoře jednoduchá hlavní funkce začíná vytvořením okna. Můžete si představit, že se tím zaregistruje třída okna, a zavolá se CreateWindow k vytvoření hlavního okna plochy. CreateDispatcherQueueController funkce je volána k vytvoření kontroleru fronty před voláním nějaké koroutiny s frontou dispečera vlastněným tímto kontrolerem. Tradiční zprávová smyčka je pak zadána, kde obnovení korutiny přirozeně probíhá na tomto vlákně. Po dokončení se můžete vrátit do elegantního světa koroutin pro asynchronní nebo zprávově orientovaný pracovní postup ve své aplikaci.
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
... // Begin on the calling thread...
co_await winrt::resume_foreground(queue);
... // ...resume on the dispatcher thread.
}
Volání winrt::resume_foreground bude vždy frontua potom rozbalí zásobník. Volitelně můžete také nastavit prioritu obnovení.
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
co_await winrt::resume_foreground(queue, DispatcherQueuePriority::High);
...
}
Nebo použijte výchozí pořadí front.
...
#include <winrt/Windows.System.h>
using namespace Windows::System;
...
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
co_await queue;
...
}
Poznámka:
Jak je znázorněno výše, nezapomeňte zahrnout hlavičku projekce pro obor názvů typu, který používáte pro co_await. Například Windows::UI::Core::CoreDispatcher, Windows::System::DispatcherQueuenebo Microsoft::UI::Dispatching::DispatcherQueue.
Nebo v tomto případě zjišťování vypnutí fronty a jeho řádné zpracování.
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
if (co_await queue)
{
... // Resume on dispatcher thread.
}
else
{
... // Still on calling thread.
}
}
Výraz co_await vrátí true, což znamená, že obnovení bude probíhat na vlákně dispečera. Jinými slovy, zařazení do fronty bylo úspěšné. Naopak vrátí false, aby indikovalo, že provádění zůstává ve volajícím vlákně, protože kontroler fronty je vypínán a už neobsluhuje požadavky fronty.
Takže máte velkou sílu na dosah ruky, když zkombinujete C++/WinRT s koroutiny; a zejména při vývoji desktopových aplikací ve stylu Petzold.
Zrušení asynchronní operace a zrušení zpětných volání
Funkce Windows Runtime pro asynchronní programování umožňují zrušit probíhající asynchronní akci nebo operaci. Tady je příklad, který volá StorageFolder::GetFilesAsync k načtení potenciálně velké kolekce souborů a uloží výsledný objekt asynchronní operace v datovém členu. Uživatel má možnost operaci zrušit.
// MainPage.xaml
...
<Button x:Name="workButton" Click="OnWork">Work</Button>
<Button x:Name="cancelButton" Click="OnCancel">Cancel</Button>
...
// MainPage.h
...
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.Search.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage;
using namespace Windows::Storage::Search;
using namespace Windows::UI::Xaml;
...
struct MainPage : MainPageT<MainPage>
{
MainPage()
{
InitializeComponent();
}
IAsyncAction OnWork(IInspectable /* sender */, RoutedEventArgs /* args */)
{
workButton().Content(winrt::box_value(L"Working..."));
// Enable the Pictures Library capability in the app manifest file.
StorageFolder picturesLibrary{ KnownFolders::PicturesLibrary() };
m_async = picturesLibrary.GetFilesAsync(CommonFileQuery::OrderByDate, 0, 1000);
IVectorView<StorageFile> filesInFolder{ co_await m_async };
workButton().Content(box_value(L"Done!"));
// Process the files in some way.
}
void OnCancel(IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
if (m_async.Status() != AsyncStatus::Completed)
{
m_async.Cancel();
workButton().Content(winrt::box_value(L"Canceled"));
}
}
private:
IAsyncOperation<::IVectorView<StorageFile>> m_async;
};
...
Pokud jde o implementaci zrušení, začneme jednoduchým příkladem.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;
IAsyncAction ImplicitCancelationAsync()
{
while (true)
{
std::cout << "ImplicitCancelationAsync: do some work for 1 second" << std::endl;
co_await 1s;
}
}
IAsyncAction MainCoroutineAsync()
{
auto implicit_cancelation{ ImplicitCancelationAsync() };
co_await 3s;
implicit_cancelation.Cancel();
}
int main()
{
winrt::init_apartment();
MainCoroutineAsync().get();
}
Pokud spustíte výše uvedený příklad, ImplicitCancelationAsync vytiskne jednu zprávu za sekundu po dobu tří sekund, poté se automaticky ukončí, protože bylo zrušeno. To funguje, protože při výskytu výrazu co_await korutina kontroluje, jestli byla zrušena. Pokud ano, dojde ke zkratu; pokud ne, pozastaví se normálně.
Zrušení může samozřejmě nastat, když je korutina pozastavena. Pouze když se korutina obnoví nebo dosáhne jiného co_await, zkontroluje se, zda není zrušena. Problém spočívá v potenciálně příliš hrubé latenci při reakci na zrušení.
Další možností je tedy explicitně se dotazovat na zrušení z korutiny. Aktualizujte výše uvedený příklad kódem v následujícím výpisu. V tomto novém příkladu explicitCancelationAsync načte objekt vrácený funkcí winrt::get_cancellation_token a používá ho k pravidelné kontrole, zda byl korutin zrušen. Dokud není zrušena, korutina běží neustále; jakmile je zrušena, smyčka a funkce se normálně ukončí. Výsledek je stejný jako v předchozím příkladu, ale tady dochází k ukončení explicitně a pod kontrolou.
IAsyncAction ExplicitCancelationAsync()
{
auto cancelation_token{ co_await winrt::get_cancellation_token() };
while (!cancelation_token())
{
std::cout << "ExplicitCancelationAsync: do some work for 1 second" << std::endl;
co_await 1s;
}
}
IAsyncAction MainCoroutineAsync()
{
auto explicit_cancelation{ ExplicitCancelationAsync() };
co_await 3s;
explicit_cancelation.Cancel();
}
...
Čekáním na winrt::get_cancellation_token načtete token zrušení, který má na zřeteli IAsyncAction, jež korutina produkuje vaším jménem. Operátor volání funkce na daném tokenu můžete použít k ověření stavu zrušení, což v podstatě znamená dotazování na zrušení. Pokud provádíte nějakou výpočetní operaci nebo iterujete velkou kolekcí, je to rozumná technika.
Registrace callbacku pro zrušení
Rušení prostředí Windows Runtime se automaticky nešíří do jiných asynchronních objektů. Ale (představený ve verzi 10.0.17763.0 Windows SDK ve Windows 10, verze 1809) můžete zaregistrovat volání zpětného zrušení. Jedná se o předběžný háček, pomocí kterého je možné přenášet zrušení a umožňuje integraci se stávajícími knihovnami pro souběžnost.
V tomto dalším příkladu kódu NestedCoroutineAsync provádí práci, ale nemá v sobě žádnou speciální logiku pro zrušení. CancelationPropagatorAsync je v podstatě obálka pro vnořenou korutinu; obálka předává zrušení proaktivně.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;
IAsyncAction NestedCoroutineAsync()
{
while (true)
{
std::cout << "NestedCoroutineAsync: do some work for 1 second" << std::endl;
co_await 1s;
}
}
IAsyncAction CancelationPropagatorAsync()
{
auto cancelation_token{ co_await winrt::get_cancellation_token() };
auto nested_coroutine{ NestedCoroutineAsync() };
cancelation_token.callback([=]
{
nested_coroutine.Cancel();
});
co_await nested_coroutine;
}
IAsyncAction MainCoroutineAsync()
{
auto cancelation_propagator{ CancelationPropagatorAsync() };
co_await 3s;
cancelation_propagator.Cancel();
}
int main()
{
winrt::init_apartment();
MainCoroutineAsync().get();
}
CancelationPropagatorAsync registruje funkci lambda pro vlastní callback při zrušení operace a poté čeká (pozastaví svou činnost), než se dokončí vnořená práce. Až nebo pokud bude CancellationPropagatorAsync zrušen, přenese zrušení na vnořenou korutinu. Není potřeba se dotazovat na zrušení; ani není zrušení blokováno neomezeně dlouho. Tento mechanismus je natolik flexibilní, že jej můžete použít ke spolupráci s knihovnou korutin či souběžnosti, která nepracuje s C++/WinRT.
Vykazování průběhu
Pokud coroutine vrátí IAsyncActionWithProgressnebo IAsyncOperationWithProgress, můžete načíst objekt vrácený funkcí winrt::get_progress_token a použít ho k hlášení průběhu zpět obslužné rutině průběhu. Tady je příklad kódu.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;
IAsyncOperationWithProgress<double, double> CalcPiTo5DPs()
{
auto progress{ co_await winrt::get_progress_token() };
co_await 1s;
double pi_so_far{ 3.1 };
progress.set_result(pi_so_far);
progress(0.2);
co_await 1s;
pi_so_far += 4.e-2;
progress.set_result(pi_so_far);
progress(0.4);
co_await 1s;
pi_so_far += 1.e-3;
progress.set_result(pi_so_far);
progress(0.6);
co_await 1s;
pi_so_far += 5.e-4;
progress.set_result(pi_so_far);
progress(0.8);
co_await 1s;
pi_so_far += 9.e-5;
progress.set_result(pi_so_far);
progress(1.0);
co_return pi_so_far;
}
IAsyncAction DoMath()
{
auto async_op_with_progress{ CalcPiTo5DPs() };
async_op_with_progress.Progress([](auto const& sender, double progress)
{
std::wcout << L"CalcPiTo5DPs() reports progress: " << progress << L". "
<< L"Value so far: " << sender.GetResults() << std::endl;
});
double pi{ co_await async_op_with_progress };
std::wcout << L"CalcPiTo5DPs() is complete !" << std::endl;
std::wcout << L"Pi is approx.: " << pi << std::endl;
}
int main()
{
winrt::init_apartment();
DoMath().get();
}
Pokud chcete hlásit průběh, použijte token pokroku s hodnotou pokroku jako argument. K nastavení prozatímního výsledku použijte metodu set_result() pro token průběhu.
Poznámka:
Vykazování předběžných výsledků vyžaduje C++/WinRT verze 2.0.210309.3 nebo novější.
Výše uvedený příklad se rozhodne nastavit předběžný výsledek pro každou zprávu o průběhu. Můžete se rozhodnout hlásit prozatímní výsledky kdykoli, pokud vůbec. Není nutné ji spojit se zprávou o průběhu.
Poznámka:
Není správné implementovat více než jednu obslužnou rutinu dokončení pro asynchronní akci nebo operaci. Můžete mít buď jednoho delegáta pro dokončený event, nebo to můžete co_await. Pokud máte obojí, druhý selže. Je třeba vybrat jeden z následujících dvou druhů obslužných rutin dokončení; oba současně nelze použít pro stejný asynchronní objekt.
auto async_op_with_progress{ CalcPiTo5DPs() };
async_op_with_progress.Completed([](auto const& sender, AsyncStatus /* status */)
{
double pi{ sender.GetResults() };
});
auto async_op_with_progress{ CalcPiTo5DPs() };
double pi{ co_await async_op_with_progress };
Další informace o dokončovacích rutinách najdete v tématu Typy delegátů pro asynchronní akce a operace.
Oheň a zapomenutí
Někdy máte úkol, který se dá provést souběžně s jinou prací a nemusíte čekat na dokončení úkolu (žádná jiná práce na něm nezávisí), ani ji nepotřebujete k vrácení hodnoty. V takovém případě můžete spustit úkol a zapomenout na něj. Můžete to udělat tak, že napíšete korutinu, která má návratový typ winrt::fire_and_forget (místo jednoho z asynchronních operací typu Windows Runtime nebo concurrency::task).
// main.cpp
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace std::chrono_literals;
winrt::fire_and_forget CompleteInFiveSeconds()
{
co_await 5s;
}
int main()
{
winrt::init_apartment();
CompleteInFiveSeconds();
// Do other work here.
}
winrt::fire_and_forget je také užitečné jako návratový typ zpracovače událostí, když potřebujete provádět v něm asynchronní operace. Tady je příklad (viz také silné a slabé odkazy v jazyce C++/WinRT).
winrt::fire_and_forget MyClass::MyMediaBinder_OnBinding(MediaBinder const&, MediaBindingEventArgs args)
{
auto lifetime{ get_strong() }; // Prevent *this* from prematurely being destructed.
auto ensure_completion{ unique_deferral(args.GetDeferral()) }; // Take a deferral, and ensure that we complete it.
auto file{ co_await StorageFile::GetFileFromApplicationUriAsync(Uri(L"ms-appx:///video_file.mp4")) };
args.SetStorageFile(file);
// The destructor of unique_deferral completes the deferral here.
}
První argument (odesílatele) zůstane nepojmenovaný, protože ho nikdy nepoužíváme. Z tohoto důvodu je bezpečné ji nechat jako referenci. Všimněte si ale, že argumenty jsou předány hodnotou. Viz část předávání parametrů výše.
Čekání na popisovač jádra
C++/WinRT poskytuje funkci winrt::resume_on_signal, kterou můžete použít pro pozastavení do chvíle, kdy je signalizována událost jádra. Zodpovídáte za to, aby popisovač zůstal platný, dokud se co_await resume_on_signal(h) nevrátí.
resume_on_signal to samo o sobě pro vás nemůže udělat, protože jste možná ztratili popisovač ještě předtím, než resume_on_signal začne, jak je ukázáno v tomto prvním příkladu.
IAsyncAction Async(HANDLE event)
{
co_await DoWorkAsync();
co_await resume_on_signal(event); // The incoming handle is not valid here.
}
Příchozí HANDLE je platný pouze do doby, než se funkce vrací, a tato funkce (která je korutina) se vrací v prvním bodě pozastavení (první co_await v tomto případě). Zatímco čeká na DoWorkAsync, ovládací prvek se vrátil volajícímu, volající rámec se vynul z rozsahu a už nevíte, jestli bude popisovač platný při obnovení korutiny.
Technicky vzato, naše korutina přijímá své parametry podle hodnoty, jak by měla (viz předávání parametrů výše). V tomto případě ale musíme jít o krok dál, abychom se chytli ducha tohoto vodítka (nikoli jen dopis). Musíme předat silný odkaz (jinými slovy vlastnictví) spolu s popisovačem. Tady je postup.
IAsyncAction Async(winrt::handle event)
{
co_await DoWorkAsync();
co_await resume_on_signal(event); // The incoming handle *is* valid here.
}
Předání winrt::handle hodnotou poskytuje sémantiku vlastnictví, která zajišťuje, že popisovač jádra zůstane platný po celou dobu životnosti korutiny.
Tady je postup, jak tomu říkáte korutin.
namespace
{
winrt::handle duplicate(winrt::handle const& other, DWORD access)
{
winrt::handle result;
if (other)
{
winrt::check_bool(::DuplicateHandle(::GetCurrentProcess(),
other.get(), ::GetCurrentProcess(), result.put(), access, FALSE, 0));
}
return result;
}
winrt::handle make_manual_reset_event(bool initialState = false)
{
winrt::handle event{ ::CreateEvent(nullptr, true, initialState, nullptr) };
winrt::check_bool(static_cast<bool>(event));
return event;
}
}
IAsyncAction SampleCaller()
{
handle event{ make_manual_reset_event() };
auto async{ Async(duplicate(event)) };
::SetEvent(event.get());
event.close(); // Our handle is closed, but Async still has a valid handle.
co_await async; // Will wake up when *event* is signaled.
}
Hodnotu časového limitu můžete předat resume_on_signal, jak je uvedeno v tomto příkladu.
winrt::handle event = ...
if (co_await winrt::resume_on_signal(event.get(), std::literals::2s))
{
puts("signaled");
}
else
{
puts("timed out");
}
Asynchronní časové limity se zjednodušily.
C++/WinRT silně investuje do koroutin jazyka C++. Jejich vliv na psaní konkurenčního kódu je transformační. Tato část popisuje případy, kdy podrobnosti asynchronního chování nejsou důležité, a vše, co chcete, je okamžitý výsledek. Z tohoto důvodu má implementace C++/WinRT asynchronního rozhraní IAsyncAction Prostředí Windows Runtime funkci získat podobně jako std::future.
using namespace winrt::Windows::Foundation;
int main()
{
IAsyncAction async = ...
async.get();
puts("Done!");
}
funkce se zablokuje bez omezení času, dokud se asynchronní objekt nedokončí. Asynchronní objekty mají tendenci být velmi krátkodobé, takže to je často vše, co potřebujete.
Ale existují případy, kdy to nestačí, a potřebujete opustit čekání po uplynutí určité doby. Psaní tohoto kódu bylo vždy možné díky stavebním blokům poskytovaným prostředím Windows Runtime. Teď ale C++/WinRT usnadňuje poskytování funkce wait_for. Je také implementována v IAsyncAction a znovu je podobná tomu, co poskytuje std::future.
using namespace std::chrono_literals;
int main()
{
IAsyncAction async = ...
if (async.wait_for(5s) == AsyncStatus::Completed)
{
puts("done");
}
}
Poznámka:
wait_for v rozhraní používá std::chrono::duration, ale je omezen na určitý rozsah, který je menší než to, co poskytuje std::chrono::duration (přibližně 49,7 dne).
wait_for v tomto dalším příkladu počká přibližně pět sekund a pak zkontroluje dokončení. Pokud je porovnání příznivé, pak víte, že asynchronní objekt byl úspěšně dokončen a jste hotovi. Pokud čekáte na nějaký výsledek, můžete pokračovat zavoláním metody GetResults pro načtení výsledku.
Poznámka:
wait_for a get se vzájemně vylučují (nelze volat obě funkce současně). Každý se počítá jako číšníka asynchronní akce/operace prostředí Windows Runtime podporují pouze jednoho číšníka.
int main()
{
IAsyncOperation<int> async = ...
if (async.wait_for(5s) == AsyncStatus::Completed)
{
printf("result %d\n", async.GetResults());
}
}
Vzhledem k tomu, asynchronní objekt byl dokončen do té doby, GetResults metoda vrátí výsledek okamžitě, bez dalšího čekání. Jak vidíte, wait_for vrátí stav asynchronního objektu. Můžete ho tedy použít k jemněji odstupňovanému ovládání, jako je tento.
switch (async.wait_for(5s))
{
case AsyncStatus::Completed:
printf("result %d\n", async.GetResults());
break;
case AsyncStatus::Canceled:
puts("canceled");
break;
case AsyncStatus::Error:
puts("failed");
break;
case AsyncStatus::Started:
puts("still running");
break;
}
- Nezapomeňte, že AsyncStatus::Completed znamená, že asynchronní objekt byl úspěšně dokončen a můžete volat metodu GetResults k načtení jakéhokoli výsledku.
- AsyncStatus::Canceled znamená, že asynchronní objekt byl zrušen. Volající obvykle požaduje zrušení, takže zpracování tohoto stavu může být vzácné. Zrušený asynchronní objekt se obvykle jednoduše zahodí. Pokud chcete, můžete volat metodu GetResults, abyste znovu vyvolali výjimku zrušení.
- AsyncStatus::Error znamená, že asynchronní objekt nějakým způsobem selhal. Pokud chcete, můžete volat metodu GetResults, abyste znovu vyvolali výjimku.
- AsyncStatus::Started znamená, že asynchronní objekt je stále spuštěný. Asynchronní vzor prostředí Windows Runtime neumožňuje více čekání ani číšníků. To znamená, že nemůžete volat wait_for ve smyčce. Pokud opravdu vypršel časový limit čekání, zůstanete s několika možnostmi. Objekt můžete opustit nebo můžete dotazovat jeho stav před voláním metody GetResults k načtení jakéhokoli výsledku. V tuto chvíli je ale nejlepší objekt zahodit.
Alternativním vzorem je zkontrolovat pouze stav spuštěna nechat funkci GetResults řešit ostatní případy.
if (async.wait_for(5s) == AsyncStatus::Started)
{
puts("timed out");
}
else
{
// will throw appropriate exception if in canceled or error state
auto results = async.GetResults();
}
Asynchronní vrácení pole
Níže je příklad MIDL 3.0, který generuje chybu MIDL2025: [msg]syntaxe [context]: očekává se > nebo v blízkosti"[" .
Windows.Foundation.IAsyncOperation<Int32[]> RetrieveArrayAsync();
Důvodem je, že použití pole jako argumentu typu parametru pro parametrizované rozhraní je neplatné. Proto potřebujeme méně zřejmý způsob, jak dosáhnout cíle asynchronního předávání pole zpět z metody třídy runtime.
Pole můžete vrátit do objektu PropertyValue. Volající kód pak zruší jeho zaškrtnutí. Tady je příklad kódu, který si můžete vyzkoušet přidáním třídy runtime SampleComponent do projektu Windows Runtime komponenty (C++/WinRT) a následným použitím z projektu Core App (C++/WinRT).
// SampleComponent.idl
namespace MyComponentProject
{
runtimeclass SampleComponent
{
Windows.Foundation.IAsyncOperation<IInspectable> RetrieveCollectionAsync();
};
}
// SampleComponent.h
...
struct SampleComponent : SampleComponentT<SampleComponent>
{
...
Windows::Foundation::IAsyncOperation<Windows::Foundation::IInspectable> RetrieveCollectionAsync()
{
co_return Windows::Foundation::PropertyValue::CreateInt32Array({ 99, 101 }); // Box an array into a PropertyValue.
}
}
...
// SampleCoreApp.cpp
...
MyComponentProject::SampleComponent m_sample_component;
...
auto boxed_array{ co_await m_sample_component.RetrieveCollectionAsync() };
auto property_value{ boxed_array.as<winrt::Windows::Foundation::IPropertyValue>() };
winrt::com_array<int32_t> my_array;
property_value.GetInt32Array(my_array); // Unbox back into an array.
...
Důležitá rozhraní API
- rozhraní IAsyncAction
- rozhraní IAsyncActionWithProgress<TProgress>
- IAsyncOperation<TResult> rozhraní
- IAsyncOperationWithProgress<TResult, TProgress> rozhraní
- SyndicationClient::RetrieveFeedAsync – metoda
- winrt::fire_and_forget
- winrt::get_cancellation_token
- winrt::get_progress_token
- winrt::resume_foreground