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!
Bygga med Windows App SDK? Den här artikelns kod använder UWP-namnområden (Windows.UI.Xaml). Om ditt projekt är inriktat på WinUI 3 (Windows App SDK), ersätt Microsoft.UI.Xaml (och relaterade Microsoft.UI.*-namnområden) genomgående. Mer information finns i Mappa UWP-API:er till Windows App SDK för en fullständig migreringsguide för mappning och användargränssnitt.
Windows Runtime är ett referensberäkningssystem, och i ett sådant system är det viktigt att du känner till betydelsen av och skillnaden mellan starka och svaga referenser (och referenser som inte är några av dem, till exempel den implicita pekaren). Som du ser i det här avsnittet kan vetskapen om hur du hanterar dessa referenser på rätt sätt innebära skillnaden mellan ett tillförlitligt system som körs smidigt och ett system som kraschar oförutsägbart. Genom att tillhandahålla hjälpfunktioner som har djupt stöd i språkprojektionen möter C++/WinRT dig halvvägs i arbetet med att skapa mer komplexa system enkelt och korrekt.
Note
Med bara några få undantag är det svaga referensstödet aktiverat som standard för Windows Runtime typer som du använder eller skapar i C++/WinRT. Windows. UI. Sammansättning och Windows. Devices.Input.PenDevice är exempel på undantag, det vill säga namnområden där svagt referensstöd inte är aktiverat för dessa typer. Se även Om ditt ombud för automatisk återkallelse inte går att registrera.
Om du redigerar typer kan du läsa avsnittet Svaga referenser i C++/WinRT i det här avsnittet.
Säker åtkomst till this-pekaren i en klassmedlemskorutin
Mer information om coroutines och kodexempel finns i Samtidighet och asynkrona åtgärder med C++/WinRT.
Kodlistan nedan visar ett typiskt exempel på en coroutine som är en medlemsfunktion i en klass. Du kan kopiera och klistra in det här exemplet i de angivna filerna i ett nytt Windows konsolprogram (C++/WinRT) projekt.
// pch.h
#pragma once
#include <iostream>
#include <winrt/Windows.Foundation.h>
// main.cpp : Defines the entry point for the console application.
#include "pch.h"
using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;
struct MyClass : winrt::implements<MyClass, IInspectable>
{
winrt::hstring m_value{ L"Hello, World!" };
IAsyncOperation<winrt::hstring> RetrieveValueAsync()
{
co_await 5s;
co_return m_value;
}
};
int main()
{
winrt::init_apartment();
auto myclass_instance{ winrt::make_self<MyClass>() };
auto async{ myclass_instance->RetrieveValueAsync() };
winrt::hstring result{ async.get() };
std::wcout << result.c_str() << std::endl;
}
MyClass::RetrieveValueAsync arbetar en stund och returnerar till slut en kopia av datamedlemmen MyClass::m_value. Att anropa RetrieveValueAsync gör att ett asynkront objekt skapas, och det objektet har en implicit this-pekare (genom vilken m_value så småningom nås).
Kom ihåg att exekveringen i en korutin körs synkront ända fram till den första suspenderingspunkten, där kontrollen återlämnas till anroparen. I RetrieveValueAsync är den första co_await den första suspensionspunkten. När koroutinen återupptas (cirka fem sekunder senare, i det här fallet) kan vad som helst ha hänt med den implicita this-pekaren genom vilken vi kommer åt m_value.
Här är hela sekvensen med händelser.
- I huvudsak skapas en instans av MyClass (
myclass_instance). - Objektet
asyncskapas och pekar (via det här) påmyclass_instance. - Funktionen winrt::Windows::Foundation::IAsyncAction::get når sin första suspenderingspunkt, blockerar i några sekunder och returnerar sedan resultatet av RetrieveValueAsync.
-
RetrieveValueAsync returnerar värdet
this->m_valueför .
Steg 4 är endast säkert så länge detta är giltigt.
Men vad händer om klassinstansen förstörs innan asynkroniseringsåtgärden slutförs? Det finns alla typer av sätt som klassinstansen kan gå utanför omfånget innan den asynkrona metoden har slutförts. Men vi kan simulera den genom att ställa in klassinstansen på nullptr.
int main()
{
winrt::init_apartment();
auto myclass_instance{ winrt::make_self<MyClass>() };
auto async{ myclass_instance->RetrieveValueAsync() };
myclass_instance = nullptr; // Simulate the class instance going out of scope.
winrt::hstring result{ async.get() }; // Behavior is now undefined; crashing is likely.
std::wcout << result.c_str() << std::endl;
}
Efter den punkt där vi förstör klassinstansen ser det ut som om vi inte refererar direkt till den igen. Men naturligtvis har det asynkrona objektet en pekare till det och försöker använda det för att kopiera värdet som lagras i klassinstansen. Coroutine är en medlemsfunktion, och den förväntar sig att kunna använda den här pekaren ostraffat.
Med den här ändringen av koden stöter vi på ett problem i steg 4 eftersom klassinstansen har förstörts och detta inte längre är giltigt. Så snart det asynkrona objektet försöker komma åt variabeln i klassinstansen kraschar det (eller gör något helt odefinierat).
Lösningen är att ge den asynkrona operationen – coroutinen – en egen stark referens till klassinstansen. Så som den är skriven nu har koroutinen i praktiken en rå this-pekare till klassinstansen; men det räcker inte för att hålla klassinstansen vid liv.
Om du vill hålla klassinstansen vid liv ändrar du implementeringen av RetrieveValueAsync till den som visas nedan.
IAsyncOperation<winrt::hstring> RetrieveValueAsync()
{
auto strong_this{ get_strong() }; // Keep *this* alive.
co_await 5s;
co_return m_value;
}
En C++/WinRT-klass härleds direkt eller indirekt från mallen winrt::implements . Därför kan C++/WinRT-objektet anropa sin implements::get_strong skyddade medlemsfunktion för att hämta en stark referens till sin this-pekare. Observera att det inte finns något behov av att faktiskt använda variabeln strong_this i kodexemplet ovan. Om du bara anropar get_strong ökas referensantalet för C++/WinRT-objektet och dess implicita pekare är giltig.
Viktigt!
Eftersom get_strong är en medlemsfunktion i mallen winrt::implements struct kan du bara anropa den från en klass som direkt eller indirekt härleds från winrt::implements, till exempel en C++/WinRT-klass. Mer information om att härleda från winrt::implements och exempel finns i Skapa API:er med C++/WinRT.
Detta löser det problem som vi tidigare hade när vi kom till steg 4. Även om alla andra referenser till klassinstansen försvinner har coroutine vidtagit försiktighetsåtgärder för att garantera att dess beroenden är stabila.
Om en stark referens inte är lämplig kan du i stället anropa implementeringar::get_weak för att hämta en svag referens till detta. Bekräfta bara att du kan hämta en stark referens innan du kommer åt den här. Återigen är get_weak en medlemsfunktion i strukturmallen winrt::implements.
IAsyncOperation<winrt::hstring> RetrieveValueAsync()
{
auto weak_this{ get_weak() }; // Maybe keep *this* alive.
co_await 5s;
if (auto strong_this{ weak_this.get() })
{
co_return m_value;
}
else
{
co_return L"";
}
}
I exemplet ovan hindrar den svaga referensen inte klassinstansen från att förstöras när inga starka referenser finns kvar. Men det ger dig ett sätt att kontrollera om en stark referens kan hämtas innan du kommer åt medlemsvariabeln.
Åtkomst till den här pekaren på ett säkert sätt med ett ombud för händelsehantering
Scenariot
Allmän information om händelsehantering finns i Hantera händelser med hjälp av ombud i C++/WinRT.
I föregående avsnitt belystes potentiella livslängdsproblem inom koroutiner och samtidighet. Men om du hanterar en händelse med ett objekts medlemsfunktion, eller inifrån en lambda-funktion inuti ett objekts medlemsfunktion, måste du tänka på händelsemottagarens relativa livslängd (objektet som hanterar händelsen) och händelsekällan (objektet som lyfter händelsen). Nu ska vi titta på några kodexempel.
Kodlistan nedan definierar först en enkel EventSource-klass som genererar en allmän händelse som hanteras av eventuella ombud som har lagts till i den. Den här exempelhändelsen råkar använda ombudstypen Windows::Foundation::EventHandler, men problemen och åtgärderna här gäller alla typer av ombud.
Klassen EventRecipient tillhandahåller sedan en hanterare för händelsen EventSource::Event i form av en lambda-funktion.
// pch.h
#pragma once
#include <iostream>
#include <winrt/Windows.Foundation.h>
// main.cpp : Defines the entry point for the console application.
#include "pch.h"
using namespace winrt;
using namespace Windows::Foundation;
struct EventSource
{
winrt::event<EventHandler<int>> m_event;
void Event(EventHandler<int> const& handler)
{
m_event.add(handler);
}
void RaiseEvent()
{
m_event(nullptr, 0);
}
};
struct EventRecipient : winrt::implements<EventRecipient, IInspectable>
{
winrt::hstring m_value{ L"Hello, World!" };
void Register(EventSource& event_source)
{
event_source.Event([&](auto&& ...)
{
std::wcout << m_value.c_str() << std::endl;
});
}
};
int main()
{
winrt::init_apartment();
EventSource event_source;
auto event_recipient{ winrt::make_self<EventRecipient>() };
event_recipient->Register(event_source);
event_source.RaiseEvent();
}
Mönstret är att händelsemottagaren har en lambda-händelsehanterare med beroenden till sin this-pekare. När händelsemottagaren överlever händelsekällan överlever den dessa beroenden. Och i dessa fall, som är vanliga, fungerar mönstret bra. Vissa av dessa fall är uppenbara, till exempel när en användargränssnittssida hanterar en händelse som genereras av en kontroll som finns på sidan. Sidan överlever knappen, så hanteraren överlever också knappen. Detta gäller varje gång mottagaren äger källan (till exempel som datamedlem) eller när mottagaren och källan är syskon och ägs direkt av något annat objekt.
När du är säker på att du har ett fall där hanteraren inte kommer att överleva det här som det beror på, kan du fånga detta normalt, utan hänsyn till stark eller svag livslängd.
Men det finns fortfarande fall där detta inte överlever dess användning i en hanterare (inklusive hanterare för slutförande- och förloppshändelser som genereras av asynkrona åtgärder) och det är viktigt att veta hur de ska hanteras.
- När en händelsekälla genererar sina händelser synkront kan du återkalla din hanterare och vara säker på att du inte får fler händelser. Men för asynkrona händelser, även efter att ha återkallats (och särskilt när du återkallar inom destruktorn), kan en händelse under flygning nå objektet när det har börjat förstöras. Att hitta en plats där du kan avbryta prenumerationen innan destruktionen kan minska problemet, men fortsätta läsa för en robust lösning.
- Om du skriver en korutin för att implementera en asynkron metod är det möjligt.
- I sällsynta fall med vissa objekt i XAML-ramverket för användargränssnitt (SwapChainPanel, till exempel) är detta möjligt om mottagaren slutbehandlas utan att först avregistreras från händelsekällan.
Problemet
Nästa version av funktionen main simulerar vad som händer när händelsemottagaren förstörs (kanske när den går ur räckvidd) medan händelsekällan fortfarande utlöser händelser.
int main()
{
winrt::init_apartment();
EventSource event_source;
auto event_recipient{ winrt::make_self<EventRecipient>() };
event_recipient->Register(event_source);
event_recipient = nullptr; // Simulate the event recipient going out of scope.
event_source.RaiseEvent(); // Behavior is now undefined within the lambda event handler; crashing is likely.
}
Händelsemottagaren tas bort, men lambda-händelsehanteraren i den prenumererar fortfarande på händelsen Event. När den händelsen utlöses försöker lambdan dereferera this-pekaren, som då inte längre är giltig. En åtkomstöverträdelse beror alltså på att kod i hanteraren (eller i en coroutines fortsättning) försöker använda den.
Viktigt!
Om du stöter på en sådan här situation måste du tänka på livslängden för this-objektet och på huruvida det fångade this-objektet har längre livslängd än fångsten. Om så inte är fallet fångar du den med en stark eller svag referens, som vi visar nedan.
Eller – om det är meningsfullt för ditt scenario och om trådningsöverväganden gör det ens möjligt – är ett annat alternativ att återkalla hanteraren när mottagaren är klar med händelsen eller i mottagarens destructor. Se Återkalla ett registrerat ombud.
Det är så vi registrerar hanteraren.
event_source.Event([&](auto&& ...)
{
std::wcout << m_value.c_str() << std::endl;
});
Lambda samlar automatiskt in alla lokala variabler med referens. I det här exemplet kunde vi alltså ha skrivit detta.
event_source.Event([this](auto&& ...)
{
std::wcout << m_value.c_str() << std::endl;
});
I båda fallen fångar vi bara upp den råa this-pekaren. Och det har ingen effekt på referensräkning, så ingenting hindrar det aktuella objektet från att förstöras.
Lösningen
Lösningen är att samla in en stark referens (eller, som vi ser, en svag referens om det är lämpligare). En stark referens ökar referensantalet och håller det aktuella objektet vid liv. Du deklarerar bara en fångstvariabel (som kallas strong_this i det här exemplet) och initierar den med ett anrop till implements::get_strong, som hämtar en stark referens till vår this-pekare.
Viktigt!
Eftersom get_strong är en medlemsfunktion i mallen winrt::implements struct kan du bara anropa den från en klass som direkt eller indirekt härleds från winrt::implements, till exempel en C++/WinRT-klass. Mer information om att härleda från winrt::implements och exempel finns i Skapa API:er med C++/WinRT.
event_source.Event([this, strong_this { get_strong()}](auto&& ...)
{
std::wcout << m_value.c_str() << std::endl;
});
Du kan till och med utelämna den automatiska avbildningen av det aktuella objektet och komma åt datamedlemmen via avbildningsvariabeln i stället för via implicit detta.
event_source.Event([strong_this { get_strong()}](auto&& ...)
{
std::wcout << strong_this->m_value.c_str() << std::endl;
});
Om en stark referens inte är lämplig kan du i stället anropa implementeringar::get_weak för att hämta en svag referens till detta. En svag referens håller inte det aktuella objektet vid liv. Så bekräfta bara att du fortfarande kan hämta en stark referens från den svaga referensen innan du får åtkomst till medlemmar.
event_source.Event([weak_this{ get_weak() }](auto&& ...)
{
if (auto strong_this{ weak_this.get() })
{
std::wcout << strong_this->m_value.c_str() << std::endl;
}
});
Om du fångar en rå pekare måste du se till att objektet som pekaren pekar på hålls vid liv.
Om du använder en medlemsfunktion som ombud
Förutom lambda-funktioner gäller dessa principer även för att använda en medlemsfunktion som ombud. Syntaxen är annorlunda, så låt oss titta på lite kod. Först visas händelsehanteraren för den potentiellt osäkra medlemsfunktionen, som använder en rå this-pekare.
struct EventRecipient : winrt::implements<EventRecipient, IInspectable>
{
winrt::hstring m_value{ L"Hello, World!" };
void Register(EventSource& event_source)
{
event_source.Event({ this, &EventRecipient::OnEvent });
}
void OnEvent(IInspectable const& /* sender */, int /* args */)
{
std::wcout << m_value.c_str() << std::endl;
}
};
Det här är standard, konventionellt sätt att referera till ett objekt och dess medlemsfunktion. För att göra detta säkert kan du – från och med version 10.0.17763.0 (Windows 10 version 1809) av Windows SDK – upprätta en stark eller svag referens vid den punkt där hanteraren är registrerad. Då är händelsens mottagarobjekt känt för att fortfarande vara vid liv.
För en stark referens anropa bara get_strong i stället för den råa pekaren this. C++/WinRT säkerställer att den resulterande delegaten har en stark referens till det nuvarande objektet.
event_source.Event({ get_strong(), &EventRecipient::OnEvent });
Att samla in en stark referens innebär att objektet blir berättigat till destruktion först efter att hanteraren har avregistrerats och alla utestående återanrop har returnerats. Den garantin är dock endast giltig när händelsen utlöses. Om händelsehanteraren är asynkron måste du ge din coroutine en stark referens till klassinstansen före den första avstängningspunkten (mer information och kod finns i avsnittet Säker åtkomst till den här pekaren i ett coroutine-avsnitt för klassmedlemmar tidigare i det här avsnittet). Men det skapar en cirkelreferens mellan händelsekällan och objektet, så du måste uttryckligen bryta den genom att återkalla händelsen.
För en svag referens anropar du get_weak. C++/WinRT säkerställer att den resulterande delegaten har en svag referens. I sista stund och i bakgrunden försöker delegaten omvandla den svaga referensen till en stark och anropar endast medlemsfunktionen om det lyckas.
event_source.Event({ get_weak(), &EventRecipient::OnEvent });
Om delegaten faktiskt anropar medlemsfunktionen kommer C++/WinRT att hålla objektet vid liv tills hanteraren har returnerat. Men om din hanterare är asynkron återvänder den vid suspensionspunkter, och därför måste du se till att din korutin har en stark referens till klassinstansen före den första suspensionspunkten. Återigen, för mer information, se avsnittet Säker åtkomst till this-pekaren i en coroutine för en klassmedlem tidigare i det här ämnet.
Om medlemsfunktionen inte tillhör en Windows Runtime typ
När metoden get_strong inte är tillgänglig för dig (din typ är inte en Windows Runtime typ) kan du använda den teknik som visas i kodexemplet nedan. Här visas en vanlig C++-klass (med namnet ConsoleNetworkWatcher) som hanterar händelsen NetworkInformation.NetworkStatusChanged .
#include <winrt/Windows.Networking.Connectivity.h>
using namespace winrt;
using namespace Windows::Networking::Connectivity;
class ConsoleNetworkWatcher
{
/* any constructor, and instance methods, here*/
static void Initialize(std::shared_ptr<ConsoleNetworkWatcher> instance)
{
auto weakPointer{ std::weak_ptr{ instance } };
instance->m_statusChangedRevoker =
NetworkInformation::NetworkStatusChanged(winrt::auto_revoke,
[weakPointer](winrt::Windows::Foundation::IInspectable const& sender)
{
auto sharedPointer{ weakPointer.lock() };
if (sharedPointer)
{
sharedPointer->NetworkStatusChanged(sender);
}
});
}
void NetworkStatusChanged(winrt::Windows::Foundation::IInspectable const& sender){/* handle event here */};
private:
NetworkInformation::NetworkStatusChanged_revoker m_statusChangedRevoker;
};
Ett svagt referensexempel med SwapChainPanel::CompositionScaleChanged
I det här kodexemplet använder vi händelsen SwapChainPanel::CompositionScaleChanged som en annan bild av svaga referenser. Koden registrerar en händelsehanterare med hjälp av en lambda som fångar en svag referens till mottagaren.
winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel m_swapChainPanel;
winrt::event_token m_compositionScaleChangedEventToken;
void RegisterEventHandler()
{
m_compositionScaleChangedEventToken = m_swapChainPanel.CompositionScaleChanged([weak_this{ get_weak() }]
(Microsoft::UI::Xaml::Controls::SwapChainPanel const& sender,
Windows::Foundation::IInspectable const& object)
{
if (auto strong_this{ weak_this.get() })
{
strong_this->OnCompositionScaleChanged(sender, object);
}
});
}
void OnCompositionScaleChanged(Microsoft::UI::Xaml::Controls::SwapChainPanel const& sender,
Windows::Foundation::IInspectable const& object)
{
// Here, we know that the "this" object is valid.
}
I lamba capture-satsen skapas en tillfällig variabel som representerar en svag referens till detta. I lambdans brödtext anropas funktionen OnCompositionScaleChanged om en stark referens till this kan erhållas. På så sätt kan detta användas på ett säkert sätt i OnCompositionScaleChanged.
Svaga referenser i C++/WinRT
Ovan såg vi svaga referenser användas. I allmänhet är de bra för att bryta cykliska referenser. För den interna implementeringen av det XAML-baserade användargränssnittsramverket – på grund av ramverkets historiska utformning – är den svaga referensmekanismen i C++/WinRT nödvändig för att hantera cykliska referenser. Men utanför XAML behöver du förmodligen inte använda svaga referenser (inte för att det finns något I sig XAML-specifikt om dem). I stället bör du oftast kunna utforma dina egna C++/WinRT-API:er på ett sådant sätt att du undviker behovet av cykliska referenser och svaga referenser.
För en viss typ som du deklarerar är det inte omedelbart uppenbart för C++/WinRT om eller när svaga referenser behövs. Därför tillhandahåller C++/WinRT svagt referensstöd automatiskt på struct-mallen winrt::implements, från vilka dina egna C++/WinRT-typer direkt eller indirekt härleds. Det är pay-for-play, eftersom det inte kostar dig något om inte ditt objekt faktiskt efterfrågas för IWeakReferenceSource. Och du kan välja att uttryckligen avregistrera dig från det stödet.
Kodexempel
Mallen winrt::weak_ref struct är ett alternativ för att få en svag referens till en klassinstans.
Class c;
winrt::weak_ref<Class> weak{ c };
Du kan också använda hjälpfunktionen winrt::make_weak .
Class c;
auto weak = winrt::make_weak(c);
Att skapa en svag referens påverkar inte referensantalet för själva objektet. Det gör bara att ett kontrollblock allokeras. Kontrollblocket tar hand om implementeringen av den svaga referenssemantiken. Du kan sedan försöka höja upp den svaga referensen till en stark referens och, om den lyckas, använda den.
if (Class strong = weak.get())
{
// use strong, for example strong.DoWork();
}
Förutsatt att det fortfarande finns någon annan stark referens ökar weak_ref::get-anropet referensantalet och returnerar den starka referensen till anroparen.
Inaktivera stöd för svaga referenser
Svagt referensstöd är automatiskt. Men du kan välja att uttryckligen välja bort det stödet genom att skicka winrt::no_weak_ref marker struct som ett mallargument till basklassen.
Om du härleder direkt från winrt::implements.
struct MyImplementation: implements<MyImplementation, IStringable, no_weak_ref>
{
...
}
Om du skapar en runtimeklass.
struct MyRuntimeClass: MyRuntimeClassT<MyRuntimeClass, no_weak_ref>
{
...
}
Det spelar ingen roll var i det variadiska parameterpaketet som markörstrukturen förekommer. Om du begär en svag referens för en avaktiverad typ hjälper kompilatorn dig med "Detta är endast för svagt referensstöd".
Viktiga API:er
Windows developer