Lat initiering
Lat initiering av ett objekt innebär att dess skapande skjuts upp tills det först används. (För det här avsnittet är termerna lat initiering och lat instansiering synonyma.) Lat initiering används främst för att förbättra prestanda, undvika slösaktig beräkning och minska programminneskraven. Det här är de vanligaste scenarierna:
När du har ett objekt som är dyrt att skapa och programmet kanske inte använder det. Anta till exempel att du i minnet har ett
Customer
objekt som har enOrders
egenskap som innehåller en stor matris medOrder
objekt som, för att initieras, kräver en databasanslutning. Om användaren aldrig ber om att få visa Beställningar eller använda data i en beräkning finns det ingen anledning att använda systemminne eller databehandlingscykler för att skapa dem. Genom att användaLazy<Orders>
för att deklareraOrders
objektet för lat initiering kan du undvika att slösa bort systemresurser när objektet inte används.När du har ett objekt som är dyrt att skapa och du vill skjuta upp skapandet tills andra dyra åtgärder har slutförts. Anta till exempel att programmet läser in flera objektinstanser när det startar, men bara några av dem krävs omedelbart. Du kan förbättra programmets startprestanda genom att skjuta upp initieringen av de objekt som inte krävs förrän de nödvändiga objekten har skapats.
Även om du kan skriva din egen kod för att utföra en lat initiering rekommenderar vi att du använder Lazy<T> den i stället. Lazy<T> och dess relaterade typer stöder även trådsäkerhet och tillhandahåller en konsekvent undantagsspridningsprincip.
I följande tabell visas de typer som .NET Framework version 4 tillhandahåller för att aktivera lat initiering i olika scenarier.
Typ | Beskrivning |
---|---|
Lazy<T> | En omslutningsklass som ger lat initieringssemantik för alla klassbibliotek eller användardefinierade typer. |
ThreadLocal<T> | Lazy<T> Liknar förutom att det ger lata initieringssemantik på trådlokal basis. Varje tråd har åtkomst till sitt eget unika värde. |
LazyInitializer | Tillhandahåller avancerade static metoder (Shared i Visual Basic) för lat initiering av objekt utan att behöva använda en klass. |
Grundläggande lazy-initiering
Om du vill definiera en lat-initierad typ, till exempel , MyType
använder Lazy<MyType>
du (Lazy(Of MyType)
i Visual Basic), som du ser i följande exempel. Om inget ombud skickas i Lazy<T> konstruktorn skapas den omslutna typen med hjälp Activator.CreateInstance av när värdeegenskapen först används. Om typen inte har någon parameterlös konstruktor genereras ett körningsfel.
I följande exempel antar du att det Orders
är en klass som innehåller en matris med Order
objekt som hämtats från en databas. Ett Customer
objekt innehåller en instans av Orders
, men beroende på användaråtgärder kan det hända att data från Orders
objektet inte krävs.
// Initialize by using default Lazy<T> constructor. The
// Orders array itself is not created yet.
Lazy<Orders> _orders = new Lazy<Orders>();
' Initialize by using default Lazy<T> constructor. The
'Orders array itself is not created yet.
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)()
Du kan också skicka ett ombud i Lazy<T> konstruktorn som anropar en specifik konstruktoröverlagring på den omslutna typen vid skapandetillfället och utföra andra initieringssteg som krävs, som du ser i följande exempel.
// Initialize by invoking a specific constructor on Order when Value
// property is accessed
Lazy<Orders> _orders = new Lazy<Orders>(() => new Orders(100));
' Initialize by invoking a specific constructor on Order
' when Value property is accessed
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)(Function() New Orders(100))
När Lazy-objektet har skapats skapas ingen instans av Orders
förrän Value egenskapen för lazy-variabeln används för första gången. Vid den första åtkomsten skapas och returneras den omslutna typen och lagras för eventuell framtida åtkomst.
// We need to create the array only if displayOrders is true
if (displayOrders == true)
{
DisplayOrders(_orders.Value.OrderData);
}
else
{
// Don't waste resources getting order data.
}
' We need to create the array only if _displayOrders is true
If _displayOrders = True Then
DisplayOrders(_orders.Value.OrderData)
Else
' Don't waste resources getting order data.
End If
Ett Lazy<T> objekt returnerar alltid samma objekt eller värde som det initierades med. Därför är egenskapen Value skrivskyddad. Om Value en referenstyp lagras kan du inte tilldela ett nytt objekt till den. (Du kan dock ändra värdet för dess inställbara offentliga fält och egenskaper.) Om Value lagrar en värdetyp kan du inte ändra dess värde. Du kan dock skapa en ny variabel genom att anropa variabelkonstruktorn igen med hjälp av nya argument.
_orders = new Lazy<Orders>(() => new Orders(10));
_orders = New Lazy(Of Orders)(Function() New Orders(10))
Den nya lata instansen, som den tidigare, instansierar Orders
inte förrän dess Value egenskap först nås.
Trådsäker initiering
Som standard Lazy<T> är objekt trådsäkra. Det innebär att om konstruktorn inte anger typen av trådsäkerhet är de objekt som Lazy<T> skapas trådsäkra. I scenarier med flera trådar initierar den första tråden Value för åtkomst till egenskapen för ett trådsäkert Lazy<T> objekt den för alla efterföljande åtkomster i alla trådar, och alla trådar delar samma data. Därför spelar det ingen roll vilken tråd som initierar objektet och rasförhållandena är godartade.
Kommentar
Du kan utöka den här konsekvensen till felvillkor med hjälp av undantagscachelagring. Mer information finns i nästa avsnitt, Undantag i Lazy Objects.
I följande exempel visas att samma Lazy<int>
instans har samma värde för tre separata trådar.
// Initialize the integer to the managed thread id of the
// first thread that accesses the Value property.
Lazy<int> number = new Lazy<int>(() => Thread.CurrentThread.ManagedThreadId);
Thread t1 = new Thread(() => Console.WriteLine("number on t1 = {0} ThreadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId));
t1.Start();
Thread t2 = new Thread(() => Console.WriteLine("number on t2 = {0} ThreadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId));
t2.Start();
Thread t3 = new Thread(() => Console.WriteLine("number on t3 = {0} ThreadID = {1}", number.Value,
Thread.CurrentThread.ManagedThreadId));
t3.Start();
// Ensure that thread IDs are not recycled if the
// first thread completes before the last one starts.
t1.Join();
t2.Join();
t3.Join();
/* Sample Output:
number on t1 = 11 ThreadID = 11
number on t3 = 11 ThreadID = 13
number on t2 = 11 ThreadID = 12
Press any key to exit.
*/
' Initialize the integer to the managed thread id of the
' first thread that accesses the Value property.
Dim number As Lazy(Of Integer) = New Lazy(Of Integer)(Function()
Return Thread.CurrentThread.ManagedThreadId
End Function)
Dim t1 As New Thread(Sub()
Console.WriteLine("number on t1 = {0} threadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t1.Start()
Dim t2 As New Thread(Sub()
Console.WriteLine("number on t2 = {0} threadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t2.Start()
Dim t3 As New Thread(Sub()
Console.WriteLine("number on t3 = {0} threadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t3.Start()
' Ensure that thread IDs are not recycled if the
' first thread completes before the last one starts.
t1.Join()
t2.Join()
t3.Join()
' Sample Output:
' number on t1 = 11 ThreadID = 11
' number on t3 = 11 ThreadID = 13
' number on t2 = 11 ThreadID = 12
' Press any key to exit.
Om du behöver separata data i varje tråd använder du typen enligt beskrivningen ThreadLocal<T> senare i det här avsnittet.
Vissa Lazy<T> konstruktorer har en boolesk parameter med namnet isThreadSafe
som används för att ange om Value egenskapen ska nås från flera trådar. Om du tänker komma åt egenskapen från bara en tråd skickar du in false
för att få en blygsam prestandaförmån. Om du tänker komma åt egenskapen från flera trådar skickar du in true
för att instruera instansen Lazy<T> att korrekt hantera konkurrensförhållanden där en tråd genererar ett undantag vid initieringstillfället.
Vissa Lazy<T> konstruktorer har en LazyThreadSafetyMode parameter med namnet mode
. Dessa konstruktorer ger ytterligare ett trådsäkerhetsläge. Följande tabell visar hur trådsäkerheten för ett Lazy<T> objekt påverkas av konstruktorparametrar som anger trådsäkerhet. Varje konstruktor har högst en sådan parameter.
Trådsäkerhet för objektet | LazyThreadSafetyMode mode parameter |
Boolesk isThreadSafe parameter |
Inga trådsäkerhetsparametrar |
---|---|---|---|
Helt trådsäker; endast en tråd i taget försöker initiera värdet. | ExecutionAndPublication | true |
Ja. |
Inte trådsäkert. | None | false |
Ej tillämpbart. |
Helt trådsäker; trådar ras för att initiera värdet. | PublicationOnly | Ej tillämpbart. | Ej tillämpbart. |
Som tabellen visar är det samma sak att LazyThreadSafetyMode.ExecutionAndPublication ange för parametern mode
som att true
ange för parametern isThreadSafe
, och att LazyThreadSafetyMode.None ange är detsamma som att false
ange .
Mer information om vad Execution
och Publication
se finns i LazyThreadSafetyMode.
Om du anger LazyThreadSafetyMode.PublicationOnly kan flera trådar försöka initiera instansen Lazy<T> . Endast en tråd kan vinna det här loppet, och alla andra trådar får det värde som initierades av den lyckade tråden. Om ett undantag utlöses på en tråd under initieringen tar den tråden inte emot det värde som angetts av den lyckade tråden. Undantag cachelagras inte, så ett efterföljande försök att komma åt Value egenskapen kan resultera i en lyckad initiering. Detta skiljer sig från hur undantag behandlas i andra lägen, vilket beskrivs i följande avsnitt. Mer information finns i LazyThreadSafetyMode uppräkningen.
Undantag i lazy-objekt
Som tidigare nämnts returnerar ett Lazy<T> objekt alltid samma objekt eller värde som det initierades med, och därför Value är egenskapen skrivskyddad. Om du aktiverar undantagscachelagring utökas även den här oföränderligheten till undantagsbeteende. Om ett lazy-initialiserat objekt har undantagscachelagring aktiverat och genererar ett undantag från dess initieringsmetod när Value egenskapen först används, utlöses samma undantag vid varje efterföljande försök att komma åt Value egenskapen. Konstruktorn av den omslutna typen anropas med andra ord aldrig på nytt, inte ens i flertrådade scenarier. Därför Lazy<T> kan objektet inte utlösa ett undantag för en åtkomst och returnera ett värde för en efterföljande åtkomst.
Undantagscachelagring aktiveras när du använder en System.Lazy<T> konstruktor som använder en initieringsmetod (valueFactory
parameter), till exempel aktiveras den när du använder Lazy(T)(Func(T))
konstruktorn. Om konstruktorn också tar ett LazyThreadSafetyMode värde (mode
parameter) anger du LazyThreadSafetyMode.ExecutionAndPublication eller LazyThreadSafetyMode.None. Om du anger en initieringsmetod kan du cachelagra undantag för dessa två lägen. Initieringsmetoden kan vara mycket enkel. Den kan till exempel anropa den parameterlösa konstruktorn för T
: new Lazy<Contents>(() => new Contents(), mode)
i C#, eller New Lazy(Of Contents)(Function() New Contents())
i Visual Basic. Om du använder en System.Lazy<T> konstruktor som inte anger någon initieringsmetod cachelagras inte undantag som genereras av den parameterlösa konstruktorn för T
. Mer information finns i LazyThreadSafetyMode uppräkningen.
Kommentar
Om du skapar ett Lazy<T> objekt med isThreadSafe
konstruktorparametern inställd på false
eller mode
konstruktorparametern inställd på LazyThreadSafetyMode.Nonemåste du komma åt Lazy<T> objektet från en enda tråd eller ange en egen synkronisering. Detta gäller för alla aspekter av objektet, inklusive undantagscachelagring.
Som nämnts i föregående avsnitt, Lazy<T> objekt som skapats genom att LazyThreadSafetyMode.PublicationOnly ange behandla undantag på olika sätt. Med PublicationOnlykan flera trådar konkurrera om att initiera instansen Lazy<T> . I det här fallet cachelagras inte undantag och försök att komma åt Value egenskapen kan fortsätta tills initieringen har slutförts.
I följande tabell sammanfattas hur Lazy<T> konstruktorerna styr cachelagring av undantag.
Konstruktor | Trådsäkerhetsläge | Använder initieringsmetod | Undantag cachelagras |
---|---|---|---|
Lazy(T)() | (ExecutionAndPublication) | Nej | Nej |
Lazy(T)(Func(T)) | (ExecutionAndPublication) | Ja | Ja |
Lazy(T)(Booleskt) | True (ExecutionAndPublication) eller false (None) |
Nej | Nej |
Lazy(T)(Func(T), boolesk) | True (ExecutionAndPublication) eller false (None) |
Ja | Ja |
Lazy(T)(LazyThreadSafetyMode) | Användaren har angetts | Nej | Nej |
Lazy(T)(Func(T), LazyThreadSafetyMode) | Användaren har angetts | Ja | Nej om användaren anger PublicationOnly, annars ja. |
Implementera en lazy-initierad egenskap
Om du vill implementera en offentlig egenskap med hjälp av en lat initiering definierar du bakgrundsfältet för egenskapen som en Lazy<T>och returnerar Value egenskapen från get
egenskapens accessor.
class Customer
{
private Lazy<Orders> _orders;
public string CustomerID {get; private set;}
public Customer(string id)
{
CustomerID = id;
_orders = new Lazy<Orders>(() =>
{
// You can specify any additional
// initialization steps here.
return new Orders(this.CustomerID);
});
}
public Orders MyOrders
{
get
{
// Orders is created on first access here.
return _orders.Value;
}
}
}
Class Customer
Private _orders As Lazy(Of Orders)
Public Shared CustomerID As String
Public Sub New(ByVal id As String)
CustomerID = id
_orders = New Lazy(Of Orders)(Function()
' You can specify additional
' initialization steps here
Return New Orders(CustomerID)
End Function)
End Sub
Public ReadOnly Property MyOrders As Orders
Get
Return _orders.Value
End Get
End Property
End Class
Egenskapen Value är skrivskyddad. Därför har egenskapen som exponerar den ingen set
åtkomst. Om du behöver en läs-/skrivegenskap som backas upp av ett Lazy<T> objekt set
måste användaren skapa ett nytt Lazy<T> objekt och tilldela det till lagringsplatsen. Accessorn set
måste skapa ett lambda-uttryck som returnerar det nya egenskapsvärdet som skickades till set
accessorn och skicka lambda-uttrycket till konstruktorn för det nya Lazy<T> objektet. Nästa åtkomst av Value egenskapen kommer att orsaka initiering av den nya Lazy<T>, och dess Value egenskap returnerar därefter det nya värdet som tilldelades egenskapen. Anledningen till det här invecklade arrangemanget är att bevara de inbyggda multitrådsskydden i Lazy<T>. Annars skulle egenskapsåtkomsterna behöva cachelagras det första värdet som returneras av Value egenskapen och endast ändra det cachelagrade värdet, och du måste skriva din egen trådsäkra kod för att göra det. På grund av de ytterligare initieringar som krävs av en läs-/skrivegenskap som backas upp av ett Lazy<T> objekt kanske prestandan inte är acceptabel. Beroende på det specifika scenariot kan det dessutom krävas ytterligare samordning för att undvika konkurrensförhållanden mellan setters och getters.
Trådlokal lat initiering
I vissa scenarier med flera trådar kanske du vill ge varje tråd egna privata data. Sådana data kallas trådlokala data. I .NET Framework version 3.5 och tidigare kan du använda ThreadStatic
attributet på en statisk variabel för att göra den trådlokal. Att använda ThreadStatic
attributet kan dock leda till subtila fel. Till exempel kommer även grundläggande initieringsinstruktioner att göra att variabeln endast initieras på den första tråden som kommer åt den, som du ser i följande exempel.
[ThreadStatic]
static int counter = 1;
<ThreadStatic()>
Shared counter As Integer
I alla andra trådar initieras variabeln med dess standardvärde (noll). Som ett alternativ i .NET Framework version 4 kan du använda System.Threading.ThreadLocal<T> typen för att skapa en instansbaserad, trådlokal variabel som initieras i alla trådar av det Action<T> ombud som du anger. I följande exempel ser alla trådar som kommer åt counter
dess startvärde som 1.
ThreadLocal<int> betterCounter = new ThreadLocal<int>(() => 1);
Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)
ThreadLocal<T> omsluter objektet på ungefär samma sätt som Lazy<T>, med dessa viktiga skillnader:
Varje tråd initierar den trådlokala variabeln med hjälp av egna privata data som inte är tillgängliga från andra trådar.
Egenskapen ThreadLocal<T>.Value är skrivskyddad och kan ändras valfritt antal gånger. Detta kan påverka undantagsspridningen, till exempel kan en
get
åtgärd generera ett undantag, men nästa åtgärd kan initiera värdet.Om ingen initieringsdelegat tillhandahålls ThreadLocal<T> initieras dess omslutna typ med hjälp av standardvärdet för typen. I det här avseendet ThreadLocal<T> är det konsekvent med attributet ThreadStaticAttribute .
I följande exempel visas att varje tråd som kommer åt instansen ThreadLocal<int>
får en egen unik kopia av data.
// Initialize the integer to the managed thread id on a per-thread basis.
ThreadLocal<int> threadLocalNumber = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId);
Thread t4 = new Thread(() => Console.WriteLine("threadLocalNumber on t4 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t4.Start();
Thread t5 = new Thread(() => Console.WriteLine("threadLocalNumber on t5 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t5.Start();
Thread t6 = new Thread(() => Console.WriteLine("threadLocalNumber on t6 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t6.Start();
// Ensure that thread IDs are not recycled if the
// first thread completes before the last one starts.
t4.Join();
t5.Join();
t6.Join();
/* Sample Output:
threadLocalNumber on t4 = 14 ThreadID = 14
threadLocalNumber on t5 = 15 ThreadID = 15
threadLocalNumber on t6 = 16 ThreadID = 16
*/
' Initialize the integer to the managed thread id on a per-thread basis.
Dim threadLocalNumber As New ThreadLocal(Of Integer)(Function() Thread.CurrentThread.ManagedThreadId)
Dim t4 As New Thread(Sub()
Console.WriteLine("number on t4 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t4.Start()
Dim t5 As New Thread(Sub()
Console.WriteLine("number on t5 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t5.Start()
Dim t6 As New Thread(Sub()
Console.WriteLine("number on t6 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t6.Start()
' Ensure that thread IDs are not recycled if the
' first thread completes before the last one starts.
t4.Join()
t5.Join()
t6.Join()
'Sample(Output)
' threadLocalNumber on t4 = 14 ThreadID = 14
' threadLocalNumber on t5 = 15 ThreadID = 15
' threadLocalNumber on t6 = 16 ThreadID = 16
Trådlokala variabler parallellt.För och ForEach
När du använder Parallel.For metoden eller Parallel.ForEach metoden för att iterera över datakällor parallellt kan du använda de överlagringar som har inbyggt stöd för trådlokala data. I dessa metoder uppnås trådlokaliteten med hjälp av lokala ombud för att skapa, komma åt och rensa data. Mer information finns i How to: Write a Parallel.For Loop with Thread-Local Variables and How to: Write a Parallel.ForEach Loop with Partition-Local Variables (Så här skriver du en Parallel.ForEach-loop med partitionslokala variabler).
Använda lazy-initiering för scenarier med låg omkostnader
I scenarier där du måste latinitiera ett stort antal objekt kan du bestämma att omslutning av varje objekt i ett Lazy<T> kräver för mycket minne eller för många databehandlingsresurser. Eller så kan du ha strikta krav på hur lat initiering exponeras. I sådana fall kan du använda static
metoderna (Shared
i Visual Basic) för System.Threading.LazyInitializer klassen för att latinitiera varje objekt utan att omsluta det i en instans av Lazy<T>.
I följande exempel antar du att du i stället för att omsluta ett helt Orders
objekt i ett Lazy<T> objekt endast har latinitierade enskilda Order
objekt om de behövs.
// Assume that _orders contains null values, and
// we only need to initialize them if displayOrderInfo is true
if (displayOrderInfo == true)
{
for (int i = 0; i < _orders.Length; i++)
{
// Lazily initialize the orders without wrapping them in a Lazy<T>
LazyInitializer.EnsureInitialized(ref _orders[i], () =>
{
// Returns the value that will be placed in the ref parameter.
return GetOrderForIndex(i);
});
}
}
' Assume that _orders contains null values, and
' we only need to initialize them if displayOrderInfo is true
If displayOrderInfo = True Then
For i As Integer = 0 To _orders.Length
' Lazily initialize the orders without wrapping them in a Lazy(Of T)
LazyInitializer.EnsureInitialized(_orders(i), Function()
' Returns the value that will be placed in the ref parameter.
Return GetOrderForIndex(i)
End Function)
Next
End If
Observera i det här exemplet att initieringsproceduren anropas vid varje iteration av loopen. I scenarier med flera trådar är den första tråden som anropar initieringsproceduren den vars värde visas av alla trådar. Senare trådar anropar också initieringsproceduren, men deras resultat används inte. Om den här typen av potentiellt konkurrensvillkor inte är acceptabelt använder du överlagringen av LazyInitializer.EnsureInitialized som tar ett booleskt argument och ett synkroniseringsobjekt.