Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Opožděná inicializace objektu znamená, že jeho vytvoření je odloženo, dokud se poprvé nepoužije. (V tomto tématu jsou termíny opožděné inicializace a opožděné vytváření instancí synonymem.) Opožděná inicializace se primárně používá ke zlepšení výkonu, zabránění plýtvání výpočty a snížení požadavků programu na paměť. Toto jsou nejběžnější scénáře:
Pokud máte objekt, který je nákladný k vytvoření a program ho nemusí používat. Představte si například, že máte v paměti objekt
Customer
, který má vlastnostOrders
, jež obsahuje velké pole objektůOrder
, k jejichž inicializaci je zapotřebí připojení k databázi. Pokud uživatel nikdy nepožádá o zobrazení objednávek nebo použití dat ve výpočtu, není důvod k jeho vytvoření použít systémové paměti nebo výpočetní cykly. Pomocí deklarováníLazy<Orders>
objektuOrders
pro opožděnou inicializaci se můžete vyhnout plýtvání systémovými prostředky, když se objekt nepoužívá.Pokud máte objekt, který je nákladný k vytvoření a chcete jeho vytvoření odložit, dokud se nedokončí jiné nákladné operace. Předpokládejme například, že program při spuštění načte několik instancí objektů, ale některé z nich se vyžadují okamžitě. Výkon spuštění programu můžete zlepšit odložením inicializace objektů, které nejsou vyžadovány, dokud nebudou vytvořeny požadované objekty.
I když můžete napsat vlastní kód pro provádění opožděné inicializace, doporučujeme místo toho použít Lazy<T> . Lazy<T> a související typy také podporují bezpečnost vláken a poskytují konzistentní zásady šíření výjimek.
Následující tabulka uvádí typy, které rozhraní .NET Framework verze 4 poskytuje k povolení opožděné inicializace v různých scénářích.
Typ | Popis |
---|---|
Lazy<T> | Obalová třída, která poskytuje sémantiku opožděné inicializace pro libovolnou knihovnu tříd nebo uživatelsky definovaný typ. |
ThreadLocal<T> | Je podobný Lazy<T>, ale poskytuje opožděnou inicializaci sémantiky na úrovni místního vlákna. Každé vlákno má přístup k vlastní jedinečné hodnotě. |
LazyInitializer | Poskytuje pokročilé static metody (Shared v jazyce Visual Basic) pro inicializaci objektů na požádání bez nutnosti režie třídy. |
Základní opožděná inicializace
Chcete-li definovat opožděný inicializovaný typ, například MyType
, použijte Lazy<MyType>
(Lazy(Of MyType)
v jazyce Visual Basic), jak je znázorněno v následujícím příkladu. Pokud není v konstruktoru Lazy<T> předán žádný delegát, je zabalený typ vytvořen pomocí Activator.CreateInstance při prvním přístupu k hodnotové vlastnosti. Pokud typ nemá konstruktor bez parametrů, vyvolá se výjimka za běhu.
V následujícím příkladu předpokládejme, že Orders
je třída, která obsahuje pole Order
objektů načtených z databáze. Objekt Customer
obsahuje instanci objektu Orders
, ale v závislosti na akcích uživatele nemusí být data z objektu Orders
nutná.
// Initialize by using default Lazy<T> constructor. The
// Orders array itself is not created yet.
Lazy<Orders> _orders = new();
' 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)()
Můžete také předat delegáta v Lazy<T> konstruktoru, který vyvolá konkrétní přetížení konstruktoru na zabalený typ v době vytvoření a provést všechny další požadované inicializační kroky, jak je znázorněno v následujícím příkladu.
// Initialize by invoking a specific constructor on Order when Value
// property is accessed
Lazy<Orders> _orders = new(() => 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))
Po vytvoření objektu Lazy se nevytváří žádná instance Orders
, dokud se poprvé nepřistoupí k vlastnosti Value proměnné Lazy. Při prvním přístupu se vytvoří a vrátí zabalený typ, který se uloží pro jakýkoli budoucí přístup.
// We need to create the array only if displayOrders is true
if (s_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
Objekt Lazy<T> vždy vrátí stejný objekt nebo hodnotu, se kterou byl inicializován. Proto je vlastnost určena jen pro Value čtení. Pokud Value uložíte typ odkazu, nemůžete mu přiřadit nový objekt. (Můžete ale změnit hodnotu jeho nastavených veřejných polí a vlastností.) Pokud Value uložíte typ hodnoty, nemůžete jeho hodnotu změnit. Novou proměnnou však můžete vytvořit opětovným vyvoláním konstruktoru proměnné pomocí nových argumentů.
_orders = new Lazy<Orders>(() => new Orders(10));
_orders = New Lazy(Of Orders)(Function() New Orders(10))
Nová opožděná instance, podobně jako předchozí instance, se neskutečňuje Orders
, dokud se k její Value vlastnosti poprvé nepřistupuje.
Inicializace bezpečná pro vlákno
Ve výchozím nastavení jsou objekty Lazy<T> bezpečné pro přístup z více vláken. To znamená, že pokud konstruktor neurčí druh zabezpečení vlákna, objekty, které vytvoří, Lazy<T> jsou bezpečné pro přístup z více vláken. Ve scénářích s více vlákny inicializuje první vlákno, které přistoupí k vlastnosti objektu bezpečného pro více vláken, tuto vlastnost pro všechny následné přístupy na všech vláknech, a všechna vlákna tak sdílejí stejná data. Proto nezáleží na tom, které vlákno inicializuje objekt, a podmínky závodu jsou neškodné.
Poznámka:
Tuto konzistenci můžete rozšířit na chybové stavy pomocí ukládání výjimek do mezipaměti. Další informace najdete v další části Výjimky v opožděných objektech.
Následující příklad ukazuje, že stejná Lazy<int>
instance má stejnou hodnotu pro tři samostatná vlákna.
// Initialize the integer to the managed thread id of the
// first thread that accesses the Value property.
Lazy<int> number = new(() => Environment.CurrentManagedThreadId);
Thread t1 = new(() => Console.WriteLine($"number on t1 = {number.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
t1.Start();
Thread t2 = new(() => Console.WriteLine($"number on t2 = {number.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
t2.Start();
Thread t3 = new(() => Console.WriteLine($"number on t3 = {number.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
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.
Pokud pro každé vlákno požadujete samostatná data, použijte ThreadLocal<T> typ, jak je popsáno dále v tomto tématu.
Některé Lazy<T> konstruktory mají logický parametr s názvem isThreadSafe
, který se používá k určení, zda Value bude vlastnost přístupná z více vláken. Pokud máte v úmyslu získat přístup k vlastnosti pouze z jednoho vlákna, předejte false
, abyste získali skromnou výhodu výkonu. Pokud máte v úmyslu přistupovat k vlastnosti z více vláken, předejte true
instanci Lazy<T>, aby správně zpracovala podmínky závodu, ve kterých může jedno vlákno vyvolat výjimku v době inicializace.
Některé Lazy<T> konstruktory mají LazyThreadSafetyMode parametr s názvem mode
. Tyto konstruktory poskytují dodatečný režim bezpečnosti vláken. Následující tabulka ukazuje, jak je zabezpečení vlákna objektu Lazy<T> ovlivněno parametry konstruktoru, které určují zabezpečení vlákna. Každý konstruktor má maximálně jeden takový parametr.
Zabezpečení vlákna objektu |
LazyThreadSafetyMode
mode parametr |
isThreadSafe Logický parametr |
Žádné parametry zabezpečení vláken |
---|---|---|---|
Plně bezpečné pro více vláken; pouze jedno vlákno se pokusí inicializovat hodnotu. | ExecutionAndPublication | true |
Ano. |
Není bezpečné pro přístup z více vláken. | None | false |
Nevztahuje se. |
Plně bezpečné pro přístup z více vláken; vlákna soutěží při inicializaci hodnoty. | PublicationOnly | Nevztahuje se. | Nevztahuje se. |
Jak ukazuje tabulka, určení parametru LazyThreadSafetyMode.ExecutionAndPublication je stejné jako určení mode
parametru true
a určení isThreadSafe
je stejné jako určení LazyThreadSafetyMode.None.false
Další informace o tom, na co Execution
a Publication
odkazují, viz LazyThreadSafetyMode.
Určení LazyThreadSafetyMode.PublicationOnly umožňuje, aby se více vláken pokusilo inicializovat Lazy<T> instanci. Tento závod může vyhrát pouze jedno vlákno a všechna ostatní vlákna obdrží hodnotu, kterou inicializovalo úspěšné vlákno. Pokud je při inicializaci vyvolána výjimka ve vláknu, toto vlákno neobdrží hodnotu nastavenou úspěšným vláknem. Výjimky nejsou uloženy v mezipaměti, takže následný pokus o přístup Value k vlastnosti může vést k úspěšné inicializaci. Liší se od způsobu zpracování výjimek v jiných režimech, které jsou popsány v následující části. Další informace naleznete v výčtu LazyThreadSafetyMode .
Výjimky v lenivých objektech
Jak bylo uvedeno dříve, Lazy<T> objekt vždy vrátí stejný objekt nebo hodnotu, se kterou byl inicializován, a proto Value je vlastnost jen pro čtení. Pokud povolíte ukládání výjimek do mezipaměti, tato neměnnost se také rozšíří na chování výjimek. Pokud má opožděný inicializovaný objekt povoleno ukládání výjimek do mezipaměti a vyvolá výjimku z jeho inicializační metody při Value prvním přístupu k vlastnosti, tato stejná výjimka je vyvolána při každém následném pokusu Value o přístup k vlastnosti. Jinými slovy, konstruktor zabaleného typu se nikdy znovu nevyvolá, a to ani ve scénářích s více vlákny. Lazy<T> Objekt proto nemůže vyvolat výjimku pro jeden přístup a vrátit hodnotu pro následný přístup.
Ukládání výjimek do mezipaměti je povoleno při použití libovolného System.Lazy<T> konstruktoru, který přebírá inicializační metodu (valueFactory
parametr), například je povolena při použití konstruktoru Lazy(T)(Func(T))
. Pokud konstruktor také přebírá LazyThreadSafetyMode hodnotu (mode
parametr), zadejte LazyThreadSafetyMode.ExecutionAndPublication nebo LazyThreadSafetyMode.None. Zadání metody inicializace umožňuje ukládání výjimek do mezipaměti pro tyto dva režimy. Metoda inicializace může být velmi jednoduchá. Může například volat konstruktor bez parametrů pro T
: new Lazy<Contents>(() => new Contents(), mode)
v jazyce C# nebo New Lazy(Of Contents)(Function() New Contents())
v jazyce Visual Basic. Pokud použijete konstruktor System.Lazy<T>, který neurčí inicializační metodu, výjimky vyvolané bezparametrovým konstruktorem pro T
nejsou uloženy do mezipaměti. Další informace naleznete v výčtu LazyThreadSafetyMode .
Poznámka:
Pokud vytvoříte Lazy<T> objekt s parametrem konstruktoru isThreadSafe
nastaveným na false
nebo mode
parametrem konstruktoru nastaveným na LazyThreadSafetyMode.None, musíte k objektu přistupovat Lazy<T> z jednoho vlákna nebo poskytnout vlastní synchronizaci. To platí pro všechny aspekty objektu, včetně ukládání výjimek do mezipaměti.
Jak je uvedeno v předchozí části, objekty Lazy<T>, které jsou vytvořeny specifikací LazyThreadSafetyMode.PublicationOnly, zacházejí s výjimkami odlišně. S PublicationOnly, více vláken může soutěžit o inicializaci Lazy<T> instance. V tomto případě se výjimky neukládají do mezipaměti a pokusy o přístup Value k vlastnosti mohou pokračovat, dokud nebude inicializace úspěšná.
Následující tabulka shrnuje způsob, jakým Lazy<T> konstruktory řídí ukládání výjimek do mezipaměti.
Konstruktor | Režim zabezpečení vláken | Používá inicializační metodu. | Výjimky se ukládají do mezipaměti. |
---|---|---|---|
Lazy(T)() | (ExecutionAndPublication) | Ne | Ne |
Lazy(T)(Func(T)) | (ExecutionAndPublication) | Ano | Ano |
Lazy(T)(Boolean) |
True (ExecutionAndPublication) nebo false (None) |
Ne | Ne |
Lazy(T)(Func(T), Logická hodnota) |
True (ExecutionAndPublication) nebo false (None) |
Ano | Ano |
Lazy(T)(LazyThreadSafetyMode) | Zadané uživatelem | Ne | Ne |
Lazy(T)(Func(T), LazyThreadSafetyMode) | Zadané uživatelem | Ano | Ne, pokud uživatel určuje PublicationOnly; jinak ano. |
Implementace opožděně inicializované vlastnosti
Chcete-li implementovat veřejnou vlastnost pomocí opožděné inicializace, definujte podpůrné pole vlastnosti jako Lazy<T> a vraťte vlastnost Value z přístupové metody get
vlastnosti.
class Customer
{
private readonly 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(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
Vlastnost Value je určena jen pro čtení, a proto vlastnost, která ji zveřejňuje, nemá žádné set
příslušenství. Pokud potřebujete vlastnost pro čtení a zápis, která je zálohována objektem Lazy<T>, musí set
vytvořit nový objekt Lazy<T> a přiřadit jej do záložního úložiště. Přistupovací set
objekt musí vytvořit výraz lambda, který vrátí novou hodnotu vlastnosti, která byla předána do přístupového objektu set
, a předat tento výraz lambda konstruktoru pro nový Lazy<T> objekt. Další přístup vlastnosti Value způsobí inicializaci nového Lazy<T>a jeho Value vlastnost vrátí novou hodnotu, která byla přiřazena k vlastnosti. Důvodem tohoto konvolutovaného uspořádání je zachování vícevláknových ochrany integrovaných do Lazy<T>. Jinak by přístupové objekty vlastností musely ukládat první hodnotu vrácenou Value vlastností do mezipaměti a upravovat pouze hodnotu uloženou v mezipaměti a k tomu byste museli napsat vlastní kód bezpečný pro vlákno. Vzhledem k dalším inicializacím vyžadovaným vlastností pro čtení a zápis zálohované objektem Lazy<T> nemusí být výkon přijatelný. V závislosti na konkrétním scénáři se navíc může vyžadovat další koordinace, aby nedocházelo k podmínkám časování mezi settery a gettery.
Líná inicializace na úrovni vlákna
V některých scénářích s více vlákny můžete chtít dát každému vláknu vlastní privátní data. Taková data se nazývají vláknově lokální data. V rozhraní .NET Framework verze 3.5 a starší můžete ThreadStatic
použít atribut na statickou proměnnou, aby byla místní pro vlákno. Použití atributu ThreadStatic
ale může vést k drobným chybám. Například i základní inicializační příkazy způsobí inicializaci proměnné pouze v prvním vlákně, které k němu přistupuje, jak je znázorněno v následujícím příkladu.
[ThreadStatic]
static int s_counter = 1;
<ThreadStatic()>
Shared counter As Integer
Ve všech ostatních vláknech bude proměnná inicializována pomocí výchozí hodnoty (nula). Jako alternativu v rozhraní .NET Framework verze 4 můžete pomocí System.Threading.ThreadLocal<T> typu vytvořit místní proměnnou založenou na instanci, která je inicializována ve všech vláknech delegátem Action<T> , který zadáte. V následujícím příkladu uvidí všechna vlákna, která přistupují counter
, počáteční hodnotu 1.
ThreadLocal<int> _betterCounter = new(() => 1);
Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)
ThreadLocal<T> zabalí objekt velmi stejným způsobem jako Lazy<T>, s těmito zásadními rozdíly:
Každé vlákno inicializuje místní proměnnou vlákna pomocí vlastních privátních dat, která nejsou přístupná z jiných vláken.
Vlastnost ThreadLocal<T>.Value je pro čtení i zápis a je možné ji změnit libovolný počet krát. To může ovlivnit šíření výjimek, například jedna
get
operace může vyvolat výjimku, ale další může úspěšně inicializovat hodnotu.Pokud není zadán žádný inicializační delegát, ThreadLocal<T> inicializuje jeho zabalený typ pomocí výchozí hodnoty typu. V tomto ohledu ThreadLocal<T> je v souladu s atributem ThreadStaticAttribute .
Následující příklad ukazuje, že každé vlákno, které přistupuje k ThreadLocal<int>
instanci získá vlastní jedinečnou kopii dat.
// Initialize the integer to the managed thread id on a per-thread basis.
ThreadLocal<int> threadLocalNumber = new(() => Environment.CurrentManagedThreadId);
Thread t4 = new(() => Console.WriteLine($"threadLocalNumber on t4 = {threadLocalNumber.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
t4.Start();
Thread t5 = new(() => Console.WriteLine($"threadLocalNumber on t5 = {threadLocalNumber.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
t5.Start();
Thread t6 = new(() => Console.WriteLine($"threadLocalNumber on t6 = {threadLocalNumber.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
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
Místní proměnné vláken ve funkcích Parallel.For a Parallel.ForEach
Pokud používáte metodu Parallel.For nebo metodu Parallel.ForEach k paralelní iteraci nad zdroji dat, můžete využít přetížení s integrovanou podporou pro data místní pro vlákna. V těchto metodách je lokalita vlákna dosažena použitím delegátů lokálních pro vlákno k vytvoření, přístupu a vyčištění dat. Další informace najdete v tématu Postupy: Zápis smyčky Parallel.For s využitím vláknově lokálních proměnných a Postupy: Zápis smyčky Parallel.ForEach pomocí lokálních proměnných oddílů.
Použití opožděné inicializace pro scénáře s nízkou režií
V situacích, kdy potřebujete opožděně inicializovat velký počet objektů, se můžete rozhodnout, že zabalení každého objektu do Lazy<T> vyžaduje příliš mnoho paměti nebo příliš mnoho výpočetních prostředků. Nebo můžete mít přísné požadavky na to, jak se exponuje opožděná inicializace. V takových případech můžete použít metody static
(Shared
v jazyce Visual Basic) třídy System.Threading.LazyInitializer k líné inicializaci každého objektu bez zabalení v instanci Lazy<T>.
V následujícím příkladu předpokládejme, že namísto zabalení celého Orders
objektu do jednoho Lazy<T> objektu máte líně inicializované jednotlivé Order
objekty pouze pokud jsou vyžadovány.
// 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
V tomto příkladu si všimněte, že inicializační procedura je vyvolána při každé iteraci smyčky. Ve scénářích s více vlákny je prvním vláknem, které vyvolá inicializační proceduru, to, jehož hodnota je viditelná pro všechna vlákna. Později vlákna také vyvolají inicializační proceduru, ale jejich výsledky se nepoužívají. Pokud není přijatelná tato potenciální podmínka závodu, použijte přetížení LazyInitializer.EnsureInitialized, které přebírá logický argument a synchronizační objekt.