Anteckning
Å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.
Det här avsnittet beskriver avancerade scenarier med samtidighet och asynkronitet i C++/WinRT.
Om du vill ha en introduktion till det här ämnet läser du först Samtidighet och asynkrona åtgärder.
Avlasta arbete till Windows-trådpoolen
En coroutine är en funktion som alla andra i och med att en anropare blockeras tills en funktion returnerar körningen till den. Och den första möjligheten för en coroutine att återvända är den första co_await, co_returneller co_yield.
Innan du utför beräkningsbundet arbete i en coroutine måste du därför returnera körningen till anroparen (med andra ord introducera en suspensionspunkt) så att anroparen inte blockeras. Om du inte redan gör det genom att utföra co_awaitnågon annan åtgärd, kan du co_await funktionen winrt::resume_background. Detta återställer kontrollen till anroparen och återupptar omedelbart körningen på en tråd i trådpoolen.
Den trådpool som används i implementeringen är den Windows-trådpoolen på låg nivå, så den är optimalt effektiv.
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;
}
Programmering med trådtillhörighet i beaktande
Det här scenariot expanderar på föregående scenario. Du avlastar lite arbete till trådpoolen, men sedan vill du visa förloppet i användargränssnittet (UI).
IAsyncAction DoWorkAsync(TextBlock textblock)
{
co_await winrt::resume_background();
// Do compute-bound work here.
textblock.Text(L"Done!"); // Error: TextBlock has thread affinity.
}
Koden ovan genererar ett winrt::hresult_wrong_thread undantag, eftersom en TextBlock måste uppdateras från den tråd som skapade den, vilket är användargränssnittstråden. En lösning är att fånga den trådkontext inom vilken vår coroutine ursprungligen anropades. För att göra det instansierar du ett winrt::apartment_context-objekt, utför sedan bakgrundsarbete och co_awaitapartment_context för att växla tillbaka till anropskontexten.
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.
}
Så länge koroutinen ovanför anropas från den UI-tråd som skapade TextBlockfungerar den här tekniken. Det kommer att finnas många fall i din app där du är säker på det.
Om du vill ha en mer allmän lösning för att uppdatera användargränssnittet, som omfattar fall där du är osäker på den anropande tråden, kan co_await du använda funktionen winrt::resume_foreground för att växla till en specifik förgrundstråd. I kodexemplet nedan anger vi förgrundstråden genom att skicka det dispatcher-objekt som är associerat med TextBlock (genom att komma åt dess Dispatcher-egenskap ). Implementeringen av winrt::resume_foreground anropar CoreDispatcher.RunAsync på det dispatcher-objektet för att köra det arbete som kommer efter det i coroutine.
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.
}
Funktionen winrt::resume_foreground har en valfri prioritetsparameter. Om du använder den parametern är mönstret som visas ovan lämpligt. Om inte kan du välja att förenkla co_await winrt::resume_foreground(someDispatcherObject); till bara co_await someDispatcherObject;.
Körningskontexter, återuppta och växla i en coroutine
I stort sett kan den ursprungliga tråden försvinna efter en avbrottspunkt i en coroutine och återupptas på valfri tråd (med andra ord kan alla trådar kalla på metoden Slutförd för den asynkrona operationen).
Men om du co_await någon av de fyra asynkrona operationstyperna för Windows Runtime (IAsyncXxx) samlar C++/WinRT in samtalskontexten i det ögonblick du co_await. Och det säkerställer att du fortfarande är i den kontexten när fortsättningen återupptas. C++/WinRT gör detta genom att kontrollera om du redan använder samtalskontexten och, om inte, växla till den. Om du var på en STA-tråd innan co_await, då kommer du att vara på samma efteråt; om du var på en flertrådad lägenhet (MTA) tråd innan co_await, kommer du att vara på en sådan efteråt.
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.
}
Anledningen till att du kan lita på det här beteendet är att C++/WinRT tillhandahåller kod för att anpassa Windows Runtimes asynkrona operationstyper till C++-coroutinespråkets stöd (dessa koddelar kallas väntadaptrar). De återstående väntande typerna i C++/WinRT är helt enkelt trådpoolomslutningar och/eller hjälpverktyg. så att de slutförs på trådpoolen.
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;
}
Om du co_await någon annan typ av operation eller funktion – även inom en C++/WinRT-coroutine-implementering – tillhandahåller ett annat bibliotek adaptrarna, och du måste förstå vad dessa adaptrar gör i termer av återupptagande och kontexter.
Om du vill hålla kontextväxlingar nere till ett minimum kan du använda några av de tekniker som vi redan har sett i det här avsnittet. Låt oss se några exempel på hur man gör det. I nästa pseudokodexempel visar vi konturen för en händelsehanterare som anropar ett Windows Runtime-API för att läsa in en bild, faller till en bakgrundstråd för att bearbeta bilden och återgår sedan till användargränssnittstråden för att visa bilden i användargränssnittet.
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.
}
I det här scenariot finns det lite ineffektivitet kring anropet till StorageFile::OpenAsync. Det finns en nödvändig kontextväxling till en bakgrundstråd (så att hanteraren kan returnera körningen till anroparen) vid återupptagande varefter C++/WinRT återställer UI-trådkontexten. Men i det här fallet är det inte nödvändigt att vara i användargränssnittstråden förrän vi är på väg att uppdatera användargränssnittet. Ju fler Windows Runtime-API:er vi anropar innan vi vårt anrop till winrt::resume_background, desto mer onödiga kontextväxlar fram och tillbaka åsypps vi. Lösningen är att inte anropa några Windows Runtime-API:er innan dess. Flytta dem alla till efter 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.
}
Om du vill göra något mer avancerat, kan du skriva dina egna väntanpassare. Om du till exempel vill att en co_await ska återupptas på samma tråd som den asynkrona åtgärden slutförs på (så att det inte sker någon kontextväxling), kan du börja med att skriva await-adaptrar som liknar de som visas nedan.
Anmärkning
Kodexemplet nedan tillhandahålls endast i utbildningssyfte; det är för att hjälpa dig att börja förstå hur väntande adapters fungerar. Om du vill använda den här tekniken i din egen kodbas rekommenderar vi att du utvecklar och testar din egen await-adapterstruktur(er). Du kan till exempel skriva complete_on_any, complete_on_currentoch complete_on(dispatcher). Överväg också att göra mallar som använder IAsyncXxx-typen som en mallparameter.
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;
};
För att förstå användningen av no_switch await-adapter, behöver du först veta att när kompilatorn för C++ stöter på en co_await-uttryck letar den efter funktioner med namnen await_ready, await_suspendoch await_resume. C++/WinRT-biblioteket tillhandahåller dessa funktioner så att du får ett rimligt beteende som standard, så här.
IAsyncAction async{ ProcessFeedAsync() };
co_await async;
För att använda no_switch adaptrar, ändra bara typen av det co_await uttrycket från IAsyncXxx till no_switch, på följande sätt.
IAsyncAction async{ ProcessFeedAsync() };
co_await static_cast<no_switch>(async);
I stället för att leta efter de tre await_xxx funktioner som matchar IAsyncXxx letar C++-kompilatorn efter funktioner som matchar no_switch.
En djupgående analys av winrt::resume_foreground
Från och med C++/WinRT 2.0 pausas funktionen winrt::resume_foreground även om den anropas från dispatcher-tråden (i tidigare versioner kan den införa dödlägen i vissa scenarier eftersom den bara pausas om den inte redan finns i dispatcher-tråden).
Det aktuella beteendet innebär att du kan förlita dig på att stackavveckling och återköning sker; och det är viktigt för systemstabilitet, särskilt i lågnivåsystemkod. Den sista kodlistan i avsnittet Programmering med trådtillhörighet i åtanke ovan visar hur du utför en komplex beräkning på en bakgrundstråd och sedan växlar till lämplig UI-tråd för att uppdatera användargränssnittet (UI).
Så här ser winrt::resume_foreground ut internt.
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{ ... };
};
Det här aktuella, jämfört med tidigare, beteendet motsvarar skillnaden mellan PostMessage och SendMessage i Win32-programutveckling. PostMessage köar arbetet och spolar sedan ned stacken utan att vänta på att arbetet ska slutföras. Det kan vara viktigt att du varvar ned stacken.
Funktionen winrt::resume_foreground stödde till en början endast CoreDispatcher (som är kopplad till en CoreWindow), som introducerades före Windows 10. Vi har sedan dess introducerat en mer flexibel och effektiv dispatcher: DispatcherQueue. Du kan skapa en DispatcherQueue för dina egna syften. Tänk på det här enkla konsolprogrammet.
using namespace Windows::System;
winrt::fire_and_forget RunAsync(DispatcherQueue queue);
int main()
{
auto controller{ DispatcherQueueController::CreateOnDedicatedThread() };
RunAsync(controller.DispatcherQueue());
getchar();
}
Exemplet ovan skapar en kö (som finns i en kontrollant) på en privat tråd och skickar sedan kontrollanten till coroutinen. Coroutinen kan använda kön för att vänta (pausa och återuppta) på den privata tråden. En annan vanlig användning av DispatcherQueue är att skapa en kö i den aktuella användargränssnittstråden för en traditionell skrivbordsapp eller Win32-app.
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 };
}
Detta illustrerar hur du kan anropa och införliva Win32-funktioner i dina C++/WinRT-projekt genom att helt enkelt anropa funktionen CreateDispatcherQueueController i Win32-stil för att skapa kontrollanten och sedan överföra ägarskapet för den resulterande kökontrollanten till anroparen som ett WinRT-objekt. Så här kan du stödja en effektiv och sömlös köhantering på din befintliga Petzold-stils Win32-skrivbordsprogram.
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);
}
}
Ovan börjar den enkla huvudfunktionen med att skapa ett fönster. Du kan tänka dig att detta registrerar en fönsterklass och anropar CreateWindow för att skapa skrivbordsfönstret på den översta nivån. CreateDispatcherQueueController-funktionen anropas sedan för att skapa kökontrollanten innan man anropar en coroutine med dispatcher-kön som hanteras av denna kontrollant. Sedan går man in i en traditionell meddelandepump där coroutinen naturligt återupptas på denna tråd. När du har gjort det kan du återgå till den eleganta världen av coroutines för ditt asynkrona eller meddelandebaserade arbetsflöde i ditt program.
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
... // Begin on the calling thread...
co_await winrt::resume_foreground(queue);
... // ...resume on the dispatcher thread.
}
Anropet till winrt::resume_foreground kommer alltid att köoch sedan varva ned stacken. Du kan också ange återupptagningsprioriteten.
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
co_await winrt::resume_foreground(queue, DispatcherQueuePriority::High);
...
}
Eller använda standardköordningen.
...
#include <winrt/Windows.System.h>
using namespace Windows::System;
...
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
co_await queue;
...
}
Anmärkning
Som visas ovan, se till att inkludera projektionsrubriken för namnområdet för den typ du arbetar med co_await. Till exempel Windows::UI::Core::CoreDispatcher, Windows::System::DispatcherQueueeller Microsoft::UI::Dispatching::DispatcherQueue.
Eller i det här fallet identifiera köavstängning och hantera den på ett korrekt sätt.
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
if (co_await queue)
{
... // Resume on dispatcher thread.
}
else
{
... // Still on calling thread.
}
}
Uttrycket co_await returnerar true, som anger att återupptagandet sker i dispatcher-tråden. Med andra ord var köandet framgångsrikt. Omvänt returneras false för att indikera att körningen förblir i den anropande tråden eftersom köns kontrollant stängs av och inte längre betjänar köbegäranden.
Så, du har enorm kraft till hands när du kombinerar C++/WinRT med coroutines, särskilt när du utvecklar skrivbordsapplikationer i gammaldags Petzold-stil.
Avbryta en asynkron åtgärd och återanrop för annullering
Med Windows Runtimes funktioner för asynkron programmering kan du avbryta en asynkron åtgärd eller åtgärd under flygning. Här är ett exempel som anropar StorageFolder::GetFilesAsync för att hämta en potentiellt stor samling filer och lagrar det resulterande asynkrona åtgärdsobjektet i en datamedlem. Användaren har möjlighet att avbryta åtgärden.
// 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;
};
...
För implementeringssidan av annulleringen börjar vi med ett enkelt exempel.
// 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();
}
Om du kör exemplet ovan ser du ImplicitCancelationAsync skriva ut ett meddelande per sekund i tre sekunder, varefter det avslutas automatiskt till följd av att det avbryts. Detta fungerar eftersom en coroutine vid ett möte med ett co_await uttryck kontrollerar om det har avbrutits. Om den har gjort det, kortsluts den; då om den inte har gjort det, pausas den som vanligt.
Annullering kan naturligtvis inträffa medan koroutinen är avstängd. Endast när coroutine återupptas, eller träffar en annan co_await, kommer den att söka efter annullering. Problemet är en potentiellt alltför grov fördröjning när det gäller att svara på annullering.
Ett annat alternativ är därför att uttryckligen söka efter annullering inifrån din coroutine. Uppdatera exemplet ovan med koden i listan nedan. I det här nya exemplet hämtar ExplicitCancelationAsync objektet som returneras av funktionen winrt::get_cancellation_token och använder det för att regelbundet kontrollera om coroutine har avbrutits. Så länge det inte avbryts, loopar coroutine på obestämd tid; när den har avbrutits avslutas loopen och funktionen normalt. Resultatet är detsamma som i föregående exempel, men här sker avslutandet explicit och under kontroll.
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();
}
...
Väntar på winrt::get_cancellation_token hämtar en annulleringstoken med kunskap om IAsyncAction- som coroutine producerar för din räkning. Du kan använda funktionsanropsoperatorn på denna token för att kontrollera annulleringstillståndet — vilket innebär att i princip kontrollera om det har annullerats. Om du utför en viss beräkningsbunden åtgärd eller itererar genom en stor samling är detta en rimlig teknik.
Registrera ett återanrop för annullering
Windows Runtimes annullering flödar inte automatiskt över till andra asynkrona objekt. Men – som introducerades i version 10.0.17763.0 (Windows 10, version 1809) av Windows SDK – kan du registrera ett återanrop om annullering. Detta är en hook förhindrande genom vilken annullering kan propageras och möjliggör integration med befintliga konkurrensbibliotek.
I nästa kodexempel utför NestedCoroutineAsync arbetet, men den har ingen särskild annulleringslogik i sig. CancelationPropagatorAsync är i huvudsak en inkapsling på den inkapslade coroutine, och inkapslingen vidarebefordrar avbokningen i förväg.
// 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 registrerar en lambda-funktion för sin egen återanrop för annullering och väntar sedan (pausas) tills det kapslade arbetet har slutförts. När eller om CancellationPropagatorAsync avbryts sprids annulleringen till den kapslade koroutinen. Det finns ingen anledning att söka efter annullering. inte heller blockeras annulleringen på obestämd tid. Den här mekanismen är tillräckligt flexibel för att du ska kunna använda den med ett coroutine- eller samtidighetsbibliotek som inte har någon kännedom om C++/WinRT.
Rapportering av framsteg
Om din coroutine returnerar antingen IAsyncActionWithProgress eller IAsyncOperationWithProgress kan du hämta objektet som returneras av funktionen winrt::get_progress_token och använda det för att rapportera förloppet tillbaka till en förloppshanterare. Här är ett kodexempel.
// 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();
}
Om du vill rapportera förloppet anropar du förloppstoken med förloppsvärdet som argument. Om du vill ange ett preliminärt resultat använder du set_result() metoden på förloppstoken.
Anmärkning
Rapportering av preliminära resultat kräver C++/WinRT version 2.0.210309.3 eller senare.
Exemplet ovan väljer att ange ett preliminärt resultat för varje förloppsrapport. Du kan välja att rapportera preliminära resultat när som helst, om alls. Den behöver inte kopplas till en förloppsrapport.
Anmärkning
Det är inte korrekt att implementera fler än en kompletteringshanterare för en asynkron åtgärd eller operation. Du kan ha antingen en enda delegat för den färdiga händelsen, eller så kan du co_await den. Om du har båda, då kommer den andra att misslyckas. Någon av följande två typer av slutförandehanterare är lämplig. inte båda för samma asynkrona 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 };
Mer information om avslutningshanterare finns i Delegattyper för asynkrona åtgärder och operationer.
Skjut och glöm
Ibland har du en uppgift som kan utföras samtidigt med annat arbete, och du behöver inte vänta tills aktiviteten har slutförts (inget annat arbete beror på det), och du behöver inte heller returnera ett värde. I så fall kan du utlösa uppgiften och glömma den. Du kan göra det genom att skriva en coroutine vars returtyp är winrt::fire_and_forget (i stället för någon av de asynkrona åtgärdstyperna för Windows Runtime eller 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 är också användbart som returtyp för händelsehanteraren om du behöver utföra asynkrona åtgärder i den. Här är ett exempel (se även Starka och svaga referenser i 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.
}
Det första argumentet ( avsändaren) lämnas namnlöst eftersom vi aldrig använder det. Därför är det säkert att lämna det som referens. Dock, observera att args överförs som värde. Se avsnittet parameteröverföring ovan.
Väntar på ett kernel-handtag
C++/WinRT tillhandahåller en winrt::resume_on_signal-funktion som du kan använda för att pausa tills en kernelhändelse signaleras. Du ansvarar för att se till att ditt handtag förblir giltigt tills co_await resume_on_signal(h) returneras.
resume_on_signal själv kan inte göra det åt dig, eftersom du kan ha förlorat handtaget redan innan resume_on_signal startar, som i det här första exemplet.
IAsyncAction Async(HANDLE event)
{
co_await DoWorkAsync();
co_await resume_on_signal(event); // The incoming handle is not valid here.
}
Inkommande HANDLE- är endast giltig tills funktionen returnerar, och denna funktion (som är en coroutine) returnerar vid den första suspenderingspunkten (den första co_await i det här fallet). I väntan på DoWorkAsynchar kontrollen återvänt till anroparen, anropsramen har fallit utanför sin räckvidd, och du vet inte längre om handtaget kommer att vara giltigt när din coroutine återupptas.
Tekniskt sett tar vår coroutine emot sina parametrar efter värde, som det ska (se parameteröverföring ovan). Men i det här fallet måste vi gå ett steg längre så att vi följer andan av den vägledningen (snarare än bara dess bokstav). Vi måste skicka en stark referens (med andra ord ägarskap) tillsammans med handtaget. Så här gör du.
IAsyncAction Async(winrt::handle event)
{
co_await DoWorkAsync();
co_await resume_on_signal(event); // The incoming handle *is* valid here.
}
Att skicka en winrt::handle efter värde ger ägarskapssemantik, vilket säkerställer att kernelhandtaget förblir giltigt under coroutinens livslängd.
Så här kan du kalla den coroutine.
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.
}
Du kan skicka ett timeout-värde till resume_on_signal, som i det här exemplet.
winrt::handle event = ...
if (co_await winrt::resume_on_signal(event.get(), std::literals::2s))
{
puts("signaled");
}
else
{
puts("timed out");
}
Att hantera asynkrona timeoutar görs enkelt
C++/WinRT investeras kraftigt i C++-coroutines. Deras effekt på att skriva samtidighetskod är transformerande. I det här avsnittet beskrivs fall där information om asynkronhet inte är viktig, och allt du vill ha är resultatet där och då. Därför har C++/WinRT:s implementering av IAsyncAction asynkront gränssnitt för Windows Runtime-åtgärder en get-funktion, liknande den som erbjuds av std::future.
using namespace winrt::Windows::Foundation;
int main()
{
IAsyncAction async = ...
async.get();
puts("Done!");
}
hämta funktionsblock på obestämd tid medan asynkront objekt slutförs. Asynkrona objekt tenderar att vara mycket kortvariga, så det här är ofta allt du behöver.
Men det finns fall där det inte räcker, och du måste överge väntan efter att en tid har förflutit. Det har alltid varit möjligt att skriva koden tack vare byggstenarna som tillhandahålls av Windows Runtime. Men nu gör C++/WinRT det mycket enklare genom att tillhandahålla funktionen wait_for . Det implementeras också på IAsyncAction, och återigen liknar det som tillhandahålls av std::future.
using namespace std::chrono_literals;
int main()
{
IAsyncAction async = ...
if (async.wait_for(5s) == AsyncStatus::Completed)
{
puts("done");
}
}
Anmärkning
wait_for använder std::chrono::duration i gränssnittet, men det är begränsat till ett spann som är mindre än vad std::chrono::duration ger (ungefär 49,7 dagar).
wait_for i nästa exempel väntar i cirka fem sekunder och kontrollerar sedan om det är klart. Om jämförelsen är gynnsam vet du att asynkront objekt har slutförts och att du är klar. Om du väntar på ett visst resultat kan du helt enkelt följa det med ett anrop till metoden GetResults för att hämta resultatet.
Anmärkning
wait_for och get är ömsesidigt uteslutande (du kan inte anropa båda). De räknas var och en som en servitöroch Windows Runtime asynkrona åtgärder/åtgärder stöder endast en enda servitör.
int main()
{
IAsyncOperation<int> async = ...
if (async.wait_for(5s) == AsyncStatus::Completed)
{
printf("result %d\n", async.GetResults());
}
}
Eftersom asynkront objekt har slutförts då returnerar metoden GetResults resultatet omedelbart, utan ytterligare väntan. Som du ser returnerar wait_for tillståndet för asynkront objekt. Så du kan använda den för mer detaljerad kontroll, så här.
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;
}
- Kom ihåg att AsyncStatus::Completed innebär att asynkront objekt har slutförts och du kan anropa metoden GetResults för att hämta alla resultat.
- AsyncStatus::Avbryts innebär att asynkront objekt avbröts. En annullering begärs vanligtvis av anroparen, så det skulle vara ovanligt att hantera det här tillståndet. Normalt kastas ett avbrutet asynkront objekt helt enkelt bort. Du kan anropa metoden GetResults för att återväxa undantaget för annullering om du vill.
- AsyncStatus::Error innebär att async-objektet har misslyckats på något sätt. Du kan anropa metoden GetResults för att omkasta undantaget om du vill.
- AsyncStatus::Started innebär att async-objektet fortfarande körs. Windows Runtime-asynkroniseringsmönstret tillåter inte flera väntetider eller servitörer. Det innebär att du inte kan anropa wait_for i en loop. Om väntetiden i praktiken har överskriden tidsgräns har du några alternativ kvar. Du kan avbryta objektet, eller så kan du avsöka dess status innan du anropar metoden GetResults för att hämta ett resultat. Men det är bäst att bara ta bort objektet just nu.
Ett alternativt mönster är att endast söka efter Startadeoch låta GetResults hantera de andra fallen.
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();
}
Returnera en matris asynkront
Nedan visas ett exempel på MIDL 3.0- som ger fel MIDL2025: [msg]syntaxfel [kontext]: förväntade > eller nära "[".
Windows.Foundation.IAsyncOperation<Int32[]> RetrieveArrayAsync();
Anledningen är att det är ogiltigt att använda en matris som ett parametertypargument till ett parametriserat gränssnitt. Därför behöver vi ett mindre uppenbart sätt att uppnå vårt mål genom att asynkront returnera en matris från en metod i en körande klass.
Du kan returnera matrisen som ett PropertyValue- objekt. Den anropande koden packar sedan upp det. Här är ett kodexempel som du kan prova genom att lägga till SampleComponent-körningsklassen i ett Windows Runtime Component (C++/WinRT)-projekt och sedan använda det i (till exempel) ett Core App (C++/WinRT)-projekt.
// 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.
...
Viktiga API:er
- IAsyncAction-gränssnitt
- IAsyncActionWithProgress<TProgress-gränssnitt>
- IAsyncOperation<TResult-gränssnitt>
- IAsyncOperationWithProgress<TResult, TProgress>-gränssnittet
- SyndicationClient::RetrieveFeedAsync-metoden
- winrt::fire_and_forget
- winrt::get_cancellation_token
- winrt::get_progress_token
- winrt::resume_foreground