Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Viktigt!
Det här avsnittet beskriver begreppen coroutines och co_await, som vi rekommenderar att du använder i både användargränssnittet och i dina program som inte är användargränssnitt. För enkelhetens skull visar de flesta kodexemplen i det här introduktionsavsnittet Windows konsolprogramprojekt (C++/WinRT). De senare kodexemplen i det här avsnittet använder coroutines, men för enkelhetens skull fortsätter även konsolprogramexemplen att använda det blockerande get-funktionsanropet precis innan det avslutas, så att programmet inte avslutas innan utdata skrivs ut. Det gör du inte (anropar den blockerande funktionen get) från en UI-tråd. I stället använder du satsen co_await. De tekniker som du använder i dina UI-program beskrivs i avsnittet Avancerad samtidighet och asynkronitet.
Det här introduktionsavsnittet visar några av de sätt på vilka du både kan skapa och använda Windows Runtime asynkrona objekt med C++/WinRT. När du har läst det här avsnittet, särskilt för tekniker som du använder i dina användargränssnittsprogram, kan du även läsa Avancerad samtidighet och asynkronitet.
Asynkrona åtgärder och Windows Runtime "Async"-funktioner
Alla Windows Runtime API som kan ta mer än 50 millisekunder att slutföra implementeras som en asynkron funktion (med ett namn som slutar med "Async"). Implementeringen av en asynkron funktion initierar arbetet på en annan tråd och returnerar omedelbart med ett objekt som representerar den asynkrona åtgärden. När den asynkrona åtgärden är klar innehåller det returnerade objektet alla värden som har resulterat i arbetet. Namnområdet Windows::Foundation Windows Runtime innehåller fyra typer av asynkrona åtgärdsobjekt.
- IAsyncAction,
- IAsyncActionWithProgress<TProgress>,
- IAsyncOperation<TResult>, och
- IAsyncOperationWithProgress<TResult, TProgress>.
Var och en av dessa asynkrona åtgärdstyper projiceras till en motsvarande typ i namnområdet winrt::Windows::Foundation C++/WinRT. C++/WinRT innehåller också en intern anpassningsstruktur för await. Du använder den inte direkt, men tack vare den structen kan du skriva en co_await instruktion för att tillsammans invänta resultatet av alla funktioner som returnerar någon av dessa asynkrona åtgärdstyper. Och du kan skriva egna korrutiner som returnerar dessa typer.
Ett exempel på en asynkron Windows funktion är SyndicationClient::RetrieveFeedAsync, som returnerar ett asynkront åtgärdsobjekt av typen IAsyncOperationWithProgress<TResult, TProgress>.
Nu ska vi titta på några sätt – först blockering och sedan icke-blockering – att använda C++/WinRT för att anropa ett API som det. Bara för att illustrera de grundläggande idéerna använder vi ett Windows konsolprogramprojekt (C++/WinRT) i de närmaste kodexemplen. Tekniker som är lämpligare för ett UI-program beskrivs i Avancerad samtidighet och asynkron.
Blockera samtalstråden
Kodexemplet nedan tar emot ett asynkront åtgärdsobjekt från RetrieveFeedAsync, och det anropar get på objektet för att blockera den anropande tråden tills resultatet av den asynkrona åtgärden är tillgänglig.
Om du vill kopiera och klistra in det här exemplet direkt i huvudkällkodsfilen för ett Windows konsolprogramprojekt (C++/WinRT) anger du först Inte använda förkompilerade rubriker i projektegenskaper.
// main.cpp
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void ProcessFeed()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
// use syndicationFeed.
}
int main()
{
winrt::init_apartment();
ProcessFeed();
}
Att anropa get gör kodningen smidigare, och det passar utmärkt för konsolappar eller bakgrundstrådar där man av någon orsak kanske inte vill använda en korutin. Men den är varken parallell eller asynkron, så den lämpar sig inte för en UI-tråd (och en assert utlöses i ooptimerade byggen om du försöker använda den i en sådan). För att undvika att blockera OS-trådar så att de inte kan utföra annat nyttigt arbete behöver vi en annan teknik.
Skriv en korutin
C++/WinRT integrerar C++-coroutines i programmeringsmodellen för att ge ett naturligt sätt att tillsammans vänta på ett resultat. Du kan skapa din egen asynkrona operation i Windows Runtime genom att skriva en korutin. I kodexemplet nedan är ProcessFeedAsync en korutin.
Note
Get-funktionen finns på C++/WinRT-projektionstypen winrt::Windows::Foundation::IAsyncAction, så du kan anropa funktionen inifrån alla C++/WinRT-projekt. Funktionen visas inte som medlem i IAsyncAction-gränssnittet eftersom get inte är en del av ABI-ytan (Application Binary Interface) för den faktiska Windows Runtime typen IAsyncAction.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void PrintFeed(SyndicationFeed const& syndicationFeed)
{
for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
{
std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
}
}
IAsyncAction ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
PrintFeed(syndicationFeed);
}
int main()
{
winrt::init_apartment();
auto processOp{ ProcessFeedAsync() };
// do other work while the feed is being printed.
processOp.get(); // no more work to do; call get() so that we see the printout before the application exits.
}
En korutin är en funktion som kan avbrytas och återupptas. I koroutinen ProcessFeedAsync ovan, när satsen co_await nås, initierar koroutinen anropet till RetrieveFeedAsync asynkront och pausar sedan omedelbart sig själv samt återlämnar kontrollen till anroparen (som är main i exemplet ovan).
main kan sedan fortsätta att arbeta medan feeden hämtas och skrivs ut. När det är klart (när RetrieveFeedAsync-anropet är klart) återupptas ProcessFeedAsync-coroutine vid nästa instruktion.
Du kan aggregera en coroutine till andra coroutines. Eller så kan du anropa get för att blockera och vänta tills det har slutförts (och få resultatet om det finns ett). Eller så kan du skicka det till ett annat programmeringsspråk som stöder Windows Runtime.
Det är också möjligt att hantera händelser för slutförande och/eller förlopp för asynkrona åtgärder och operationer med hjälp av delegater. Mer information och kodexempel finns i Delegera typer för asynkrona åtgärder och åtgärder.
Som du kan se fortsätter vi i kodexemplet ovan att använda det blockerande anropet till get precis innan vi lämnar main. Men det är bara så att programmet inte avslutas innan utdata skrivs ut.
Returnera asynkront en Windows Runtime typ
I nästa exempel omsluter vi ett anrop till RetrieveFeedAsync, för en specifik URI, för att ge oss en RetrieveBlogFeedAsync-funktion som asynkront returnerar en SyndicationFeed.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void PrintFeed(SyndicationFeed const& syndicationFeed)
{
for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
{
std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
}
}
IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> RetrieveBlogFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
return syndicationClient.RetrieveFeedAsync(rssFeedUri);
}
int main()
{
winrt::init_apartment();
auto feedOp{ RetrieveBlogFeedAsync() };
// do other work.
PrintFeed(feedOp.get());
}
I exemplet ovan returnerar RetrieveBlogFeedAsync en IAsyncOperationWithProgress, som har både förlopp och ett returvärde. Vi kan utföra annat arbete medan RetrieveBlogFeedAsync gör sin grej och hämtar feeden. Sedan anropar vi get på det asynkrona åtgärdsobjektet för att blockera, vänta tills det har slutförts och sedan hämta resultatet av åtgärden.
Om du asynkront returnerar en Windows Runtime typ bör du returnera en IAsyncOperation<TResult> eller en IAsyncOperationWithProgress<TResult, TProgress>. Alla förstaparts- eller tredjeparts-runtimeklasser räknas, liksom alla typer som kan skickas till eller från en Windows Runtime-funktion (till exempel int, eller winrt::hstring). Kompilatorn hjälper dig med felet "T måste vara WinRT-typ" om du försöker använda någon av dessa asynkrona åtgärdstyper med en icke-Windows Runtime typ.
Om en coroutine inte har minst en co_await -instruktion måste den ha minst en co_return eller en co_yield -instruktion för att kunna kvalificeras som en coroutine. Det kommer att finnas fall där din korutin kan returnera ett värde utan någon asynkronitet, och därmed utan att blockera eller byta kontext. Här är ett exempel som gör det (andra gången och därefter) genom att lagra ett värde i cache.
winrt::hstring m_cache;
IAsyncOperation<winrt::hstring> ReadAsync()
{
if (m_cache.empty())
{
// Asynchronously download and cache the string.
}
co_return m_cache;
}
Returnera asynkront en typ som inte ärWindows-Runtime
Om du returnerar en typ asynkront som inte är en Windows Runtime-typ, bör du returnera en Parallel Patterns Library (PPL) concurrency::task. Vi rekommenderar concurrency::task eftersom det ger bättre prestanda och bättre framtida kompatibilitet än std::future.
Tip
Om du inkluderar <pplawait.h>, kan du använda concurrency::task som en korutintyp.
// main.cpp
#include <iostream>
#include <ppltasks.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
concurrency::task<std::wstring> RetrieveFirstTitleAsync()
{
return concurrency::create_task([]
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
return std::wstring{ syndicationFeed.Items().GetAt(0).Title().Text() };
});
}
int main()
{
winrt::init_apartment();
auto firstTitleOp{ RetrieveFirstTitleAsync() };
// Do other work here.
std::wcout << firstTitleOp.get() << std::endl;
}
Parameteröverföring
För synkrona funktioner bör du använda const& parametrar som standard. På så sätt undviks omkostnaderna för kopior (vilket inbegriper referensräkning, och det innebär sammankopplade steg och minskningar).
// Synchronous function.
void DoWork(Param const& value);
Men du kan stöta på problem om du skickar en referensparameter till en coroutine.
// NOT the recommended way to pass a value to a coroutine!
IASyncAction DoWorkAsync(Param const& value)
{
// While it's ok to access value here...
co_await DoOtherWorkAsync(); // (this is the first suspension point)...
// ...accessing value here carries no guarantees of safety.
}
I en korutin är körningen synkron fram till den första suspenderingspunkten, där styrningen återgår till anroparen och anropsramen upphör att vara giltig. När korutinen återupptas kan källvärdet som en referensparameter hänvisar till ha förändrats på vilket sätt som helst. Från coroutines perspektiv har en referensparameter okontrollerad livslängd. I exemplet ovan är det därför säkert att komma åt värdet fram till co_await, men inte efter det. Om värdet destrueras av anroparen leder försök att komma åt det i koroutinen därefter till minneskorruption. Vi kan inte heller skicka värdet på ett säkert sätt till DoOtherWorkAsync om det finns någon risk för att funktionen i sin tur pausar och sedan försöker använda värdet när den har återupptas.
För att göra parametrarna säkra att använda efter att exekveringen har pausats och återupptagits bör dina koroutiner som standard skicka parametrar med värde för att säkerställa att de fångas med värde och undvika livstidsproblem. Fall där du kan avvika från den vägledningen eftersom du är säker på att det är säkert att göra det kommer att vara sällsynta.
// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&
Att skicka efter värde kräver att argumentet är billigt att flytta eller kopiera. och det är vanligtvis fallet för en smart pekare.
Det kan också hävdas att (om du inte vill flytta värdet) är det bra praxis att skicka med const-värde. Det kommer inte att ha någon effekt på källvärdet som du gör en kopia från, men det gör avsikten tydlig och hjälper till om du oavsiktligt ändrar kopian.
// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);
Se även Standardmatriser och vektorer, som handlar om hur du skickar en standardvektor till en asynkron anropare.
Om du inte kan ändra din coroutines signatur, men du kan ändra implementeringen, kan du göra en lokal kopia före den första co_await.
IASyncAction DoWorkAsync(Param const& value)
{
auto safe_value = value;
// It's ok to access both safe_value and value here.
co_await DoOtherWorkAsync();
// It's ok to access only safe_value here (not value).
}
Om Param är dyr att kopiera extraherar du bara de delar du behöver före den första co_await.
IASyncAction DoWorkAsync(Param const& value)
{
auto safe_data = value.data;
// It's ok to access safe_data, value.data, and value here.
co_await DoOtherWorkAsync();
// It's ok to access only safe_data here (not value.data, nor value).
}
Säker åtkomst till this-pekaren i en klassmedlemskorutin
Se Starka och svaga referenser i C++/WinRT.
Viktiga API:er
- concurrency::task class
- IAsyncAction-gränssnitt
- IAsyncActionWithProgress<TProgress-gränssnitt>
- IAsyncOperation<TResult-gränssnitt>
- IAsyncOperationWithProgress<TResult, TProgress-gränssnitt>
- Metoden SyndicationClient::RetrieveFeedAsync
- SyndicationFeed-klass
Relaterade ämnen
Windows developer