Dela via


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 en Orders egenskap som innehåller en stor matris med Order 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ända Lazy<Orders> för att deklarera Orders 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 , MyTypeanvä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 LazyThreadSafetyModemode 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 falseange .

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.

Se även