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.
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.
Lägga över arbete på Windows-trådpoolen
En coroutine är precis som vilken annan funktion som helst på så sätt att den som anropar den blockeras tills funktionen lämnar tillbaka kontrollen. Och den första möjligheten för en coroutine att återvända är den första co_await, co_returneller co_yield.
Så innan du utför beräkningstungt arbete i en coroutine måste du lämna tillbaka exekveringen till anroparen (med andra ord införa en suspenderingspunkt), så att anroparen inte blockeras. Om du inte redan gör det genom co_awaitatt utföra någon annan åtgärd kan co_await du funktionen winrt::resume_background . Det returnerar kontrollen till anroparen och fortsätter sedan omedelbart att köras på en trådpoolstråd.
Den trådpool som används i implementeringen är Windows trådpool 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ådaffinitet i åtanke
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 tråden som skapade den, vilket är användargränssnittstråden. En lösning är att fånga upp den trådkontext i vilken vår korutin ursprungligen anropades. För att göra det instansierar du ett winrt::apartment_context-objekt , utför bakgrundsarbete och sedan co_awaitapartment_context för att växla tillbaka till samtalskontexten.
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 coroutine ovan anropas från användargränssnittstråden som skapade TextBlock fungerar 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 den dispatcher-kö som är associerad med TextBlock (genom att komma åt dess DispatcherQueue-egenskap ). Implementeringen av winrt::resume_foreground anropar DispatcherQueue.TryEnqueue på det dispatcherköobjektet för att köra det arbete som följer efter det i korutinen.
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.DispatcherQueue());
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, återupptagning och växling i en korutin
Grovt sett kan den ursprungliga exekveringstråden efter en suspensionspunkt i en korutin upphöra att gälla, och körningen kan återupptas på vilken tråd som helst (med andra ord kan vilken tråd som helst anropa metoden Completed för den asynkrona operationen).
Men om du co_await har någon av de fyra Windows Runtime asynkrona åtgärdstyperna (IAsyncXxx) samlar C++/WinRT in samtalskontexten vid den tidpunkt då 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 (single-threaded apartment) före co_await, är du på samma tråd efteråt. Om du var på en MTA-tråd (multi-threaded apartment) före co_await, är du 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å detta beteende är att C++/WinRT tillhandahåller kod för att anpassa dessa typer för asynkrona åtgärder i Windows Runtime till C++-språkstödet för korutiner (dessa kodavsnitt 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 använder någon annan typ – även i en C++/WinRT-korutinimplementering – är det ett annat bibliotek som tillhandahåller adaptrarna, och du behöver förstå vad dessa adaptrar gör i fråga om återupptagning 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 huvuddragen i en händelsehanterare som anropar ett Windows Runtime-API för att ladda in en bild, växlar till en bakgrundstråd för att bearbeta bilden och sedan återgår till UI-trå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->DispatcherQueue());
// 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 vårt anrop till winrt::resume_background, desto mer onödiga kontextväxlar fram och tillbaka ådrar vi oss. Lösningen är att inte anropa några Windows Runtime API:er innan dess. Flytta dem alla 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->DispatcherQueue());
// 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 då skriva egna await-adaptrar. 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 ingen kontextväxling sker), kan du börja med att skriva await-adaptrar som liknar dem som visas nedan.
Note
Kodexemplet nedan tillhandahålls endast för utbildningsändamål; det är tänkt att hjälpa dig att börja förstå hur await-adaptrar fungerar. Om du vill använda den här tekniken i din egen kodbas, rekommenderar vi att du utvecklar och testar egna adapterstrukturer för await. Du kan till exempel skriva complete_on_any, complete_on_current och 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å hur man använder no_switch-await-adaptrar måste du först känna till att när C++-kompilatorn stöter på ett co_await-uttryck letar den efter funktioner som kallas await_ready, await_suspend och 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 await-adaptrarna no_switch ändrar du bara typen på uttrycket co_await från IAsyncXxx till no_switch, så här.
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 djupare genomgång 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 nuvarande beteendet innebär att du kan lita på att stackavveckling och omköning sker, och det är viktigt för systemstabiliteten, särskilt i systemkod på låg nivå. 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 ursprungligen CoreDispatcher (knuten till en CoreWindow), som introducerades före Windows 10. I WinUI 3- och Windows App SDK-appar använder du DispatcherQueue i stället. 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. Korutinen kan använda kön för att invänta (pausa och återuppta) i den privata tråden. En annan vanlig användning av DispatcherQueue är att skapa en kö på den aktuella UI-trå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. Detta är också precis så här du kan möjliggöra effektiv och sömlös köhantering i din befintliga Petzold-liknande Win32-skrivbordsapplikation.
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. Funktionen CreateDispatcherQueueController anropas sedan för att skapa kökontrollanten innan du anropar lite coroutine med den dispatcher-kö som ägs av den här kontrollanten. Därefter startas en traditionell meddelandepump där återupptagandet av korutinen naturligt sker 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öa 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;
...
}
Note
Som visas ovan bör du se till att inkludera projektionsheadern för namnområdet för den typ som du co_await-ar. Till exempel Windows::System::DispatcherQueue eller 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öläggningen lyckad. Omvänt returneras false för att ange att körningen fortsätter på den anropande tråden eftersom köns styrenhet håller på att stängas ned och inte längre hanterar begäranden till kön.
Så har du många möjligheter till hands när du kombinerar C++/WinRT med korutiner, och särskilt när du ägnar dig åt klassisk skrivbordsprogramutveckling i Petzold-stil.
Att avbryta en asynkron åtgärd och återanrop vid avbrott
Med Windows Runtime 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 Microsoft::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 korutin, när den stöter på ett co_await-uttryck, kontrollerar om den har avbrutits. Om den har det, kortsluts den; och om det inte har gjort det, så pausas det som vanligt.
Avbrytning kan naturligtvis ske medan koroutinen är suspenderad. 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 det objekt som returneras av funktionen winrt::get_cancellation_token och använder det för att regelbundet kontrollera om korutinen har avbrutits. Så länge den inte avbryts körs korutinen i en oändlig loop; när den avbryts avslutas loopen och funktionen normalt. Resultatet är detsamma som i föregående exempel, men här sker avslutningen uttryckligen och på ett kontrollerat sätt.
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();
}
...
Att vänta på winrt::get_cancellation_token hämtar en annulleringstoken som känner till den IAsyncAction som korutinen producerar åt dig. Du kan använda funktionsanropsoperatorn på tokenet för att kontrollera annulleringstillståndet – i praktiken genom att regelbundet kontrollera om annullering har begärts. 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
Annullering i Windows Runtime överförs inte automatiskt till andra asynkrona objekt. Men – i version 10.0.17763.0 (Windows 10, version 1809) av Windows SDK – infördes möjligheten att registrera ett återanrop för avbrytande. Detta är en förebyggande krok med vilken annullering kan spridas och gör det möjligt att integrera med befintliga samtidighetsbibliotek.
I nästa kodexempel utför NestedCoroutineAsync arbetet, men den har ingen särskild annulleringslogik i sig. CancelationPropagatorAsync är i huvudsak en omslutning runt den nästlade coroutinen; omslutningen vidarebefordrar avbrott 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 callback för annullering och suspenderas sedan i väntan på att det nästlade arbetet ska slutföras. 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 för att samverka med ett korutin- eller samtidighetsbibliotek som inte vet något om C++/WinRT.
Rapportera 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.
Note
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.
Note
Det är inte korrekt att implementera fler än en slutförandehanterare för en asynkron åtgärd eller åtgärd. Du kan antingen ha en enda delegat för dess slutförandehändelse, eller så kan du co_await den. Om du har båda, 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 slutförandehanterare finns i Delegera typer för asynkrona åtgärder och åtgärder.
Avfyra 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 korutin vars returtyp är winrt::fire_and_forget (i stället för någon av Windows Runtime-typerna för asynkrona åtgärder 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 när 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. Observera dock att args skickas som värde. Se avsnittet Parameter-passing 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 handtaget förblir giltigt tills du co_await resume_on_signal(h) returnerar.
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.
}
Det inkommande HANDLE-värdet är endast giltigt tills funktionen returnerar, och den här funktionen (som är en korutin) returnerar vid den första suspensionspunkten (den första co_await i det här fallet). I väntan på DoWorkAsync har kontrollen återvänt till anroparen, samtalsramen har gått utanför omfånget och du vet inte längre om handtaget är giltigt när din coroutine återupptas.
Tekniskt sett får vår korutin sina parametrar med värdeöverföring, precis som den ska (se Parameter-passing ovan). Men i det här fallet måste vi gå ett steg längre så att vi följer andan i den vägledningen (snarare än bara bokstaven). 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");
}
Asynkrona timeouts på ett enkelt sätt
C++/WinRT bygger i hög grad på C++-korutiner. Deras effekt på skrivandet av kod för samtidighet är omvälvande. 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-Windows Runtime asynkrona åtgärdsgränssnitt en get-funktion som liknar den som tillhandahålls av std::future.
using namespace winrt::Windows::Foundation;
int main()
{
IAsyncAction async = ...
async.get();
puts("Done!");
}
get-funktionen blockeras på obestämd tid medan det asynkrona objektet 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");
}
}
Note
wait_for använder std::chrono::d uration i gränssnittet, men det är begränsat till något intervall som är mindre än vad std::chrono::d uration ger (ungefär 49,7 dagar).
Wait_for i nästa exempel väntar i cirka fem sekunder och kontrollerar sedan slutförandet. 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.
Note
wait_for och get är ömsesidigt exklusiva (du kan inte anropa båda två). De räknas var och en som en väntande anropare, och asynkrona åtgärder/operationer i Windows Runtime stöder endast en väntande anropare.
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::Canceled innebär att async-objektet avbröts. En annullering begärs vanligtvis av anroparen, så det skulle vara ovanligt att hantera det här tillståndet. Vanligtvis kasseras ett annullerat asynkront objekt. Du kan anropa metoden GetResults för att utlösa annulleringsundantaget på nytt 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 utlösa undantaget på nytt om du vill.
- AsyncStatus::Started innebär att async-objektet fortfarande körs. Det Windows Runtime asynkrona mö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äntan i praktiken har löpt ut återstår några alternativ. 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 Startad och 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();
}
Att returnera en array asynkront
Nedan följer ett exempel på MIDL 3.0 som genererar felet MIDL2025: [msg]syntaxfel [kontext]: förväntar sig > 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å målet att asynkront skicka tillbaka en matris från en körningsklassmetod.
Du kan returnera matrisen i ett PropertyValue-objekt . Den anropande koden avboxar den sedan. Här är ett kodexempel som du kan prova genom att lägga till klassen SampleComponent runtime i ett Windows Runtime komponentprojekt (C++/WinRT) och sedan använda det från (till exempel) ett tomt program, paketerat projekt (WinUI 3 i Desktop).
// 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änssnitt>
- Metoden SyndicationClient::RetrieveFeedAsync
- winrt::fire_and_forget
- winrt::get_cancellation_token
- winrt::get_progress_token
- winrt::resume_foreground
Relaterade ämnen
Windows developer