Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Cutting Edge

Non preoccupatevi: chi va piano, va sano e va lontano

Dino Esposito

Dino EspositoNello sviluppo di software pigrizia termine si riferisce a ritardare determinate attività dispendiose purché possibile molto più in relazione a idleness. Pigrizia software è ancora sulle eventuali operazioni, ma significa che qualsiasi operazione avrà luogo solo quando è necessario per completare una determinata attività. A questo proposito pigrizia è un motivo importante nello sviluppo di software e può essere applicata correttamente a una varietà di scenari, tra cui progettazione e implementazione.

Ad esempio, una delle procedure di codifica fondamentali della metodologia Extreme Programming viene riepilogata semplicemente come “ È Aren't Gonna Need It, ” che è un invito esplicito per essere lento e incorporare nella base di codice solo le funzionalità necessarie, e solo quando necessario.

Una nota diverso durante l'implementazione di una classe, si desideri essere lenta quando si tratta di caricamento dei dati da un'origine non è così economica per l'accesso. Motivo caricamento lento, infatti, illustra la soluzione comunemente accettata che un membro di una classe definita ma mantenere vuote fino a quando il contenuto è effettivamente richiesto da altro codice client. Caricamento lazy si adatta perfettamente nel contesto di mapping relazionale a oggetti (ORM) strumenti quali l'Entity Framework e NHibernate. Strumenti ORM consente di mappare le strutture di dati tra il mondo orientato ad oggetti e database relazionali. In questo contesto, caricamento lazy si riferisce alla capacità del framework di caricare, per
esempio ordini per un cliente solo quando il codice tenta di leggere la proprietà di insieme Orders esposta su una classe Customer.

Tuttavia, il caricamento lazy non limitato a scenari di implementazione specifica quali programmazione ORM. Inoltre, il caricamento lazy è non ottenere un'istanza di alcuni dati prima di diventare effettivamente utile. Caricamento lento, in altre parole, riguarda la presenza di logica speciale factory che tiene traccia di ciò che è necessario creare e crea automaticamente quando il contenuto viene richiesto alla fine.

In Microsoft .NET Framework gli sviluppatori abbiamo tempo era necessario implementare manualmente qualsiasi comportamento lazy nelle nostre classi. È non stata mai macchinari incorporato per semplificare questa attività. Non solo l'avvento di .NET Framework 4, vale a dire, in cui è possibile iniziare
Sfruttando la nuova classe Lazy <T>.

Soddisfare il Lazywriter di <T> Class

Lazy <T> è una speciale factory che utilizzare come wrapper attorno a un oggetto di un determinato tipo t. Il wrapper Lazy <T> rappresenta un proxy live per un'istanza della classe che non esista ancora. Esistono molti motivi per utilizzare tali wrapper lenta, il più importante dei quali è miglioramento delle prestazioni. Con lenta inizializzazione degli oggetti, è possibile evitare i calcoli non sono strettamente necessaria, pertanto ridurre il consumo di memoria. Se applicati correttamente, lazy la creazione di istanze di oggetti può essere anche uno strumento formidable per avviare più rapidamente delle applicazioni. Il codice riportato di seguito viene illustrato come inizializzare un oggetto in modo lento:

var container = new Lazy<DataContainer>();

In questo esempio la classe DataContainer indica un oggetto contenitore di dati semplice che fa riferimento a matrici di altri oggetti. Subito dopo aver richiamato l'operatore new su un'istanza Lazy <T>, Indietro è un'istanza diretta della classe Lazy <T>; in nessun caso si avrà un'istanza del tipo specificato t. Se è necessario passare un'istanza di DataContainer ai membri di altre classi, è necessario modificare la firma di questi membri utilizzare Lazy <DataContainer>, simile al seguente:

void ProcessData(Lazy<DataContainer> container);

Quando l'istanza effettiva del DataContainer ottenere creato in modo che il programma può funzionare con i dati che necessari? Let’s necessario esaminare l'interfaccia di programmazione pubblica della classe Lazy <T>. L'interfaccia pubblica è piuttosto esiguo include solo due proprietà: Valore e IsValue ­ create. La proprietà Value restituisce il valore corrente dell'istanza associata al tipo lazy, se presente. La proprietà è definita come segue:

public T Value 
{
  get { ... }
}

La proprietà IsValueCreated restituisce un valore Boolean e indica se è stata creata un'istanza di tipo lazy. Di seguito è tratto dal relativo codice sorgente:

public bool IsValueCreated
{
  get
  {
    return ((m_boxed != null) && (m_boxed is Boxed<T>));
  }
}

Il membro m_boxed è un membro privato e volatile interno della classe Lazy <T> contenente l'istanza effettiva del tipo T, se presente. Pertanto, IsValueCreated verifica semplicemente se un'istanza diretta di T esiste e restituisce una risposta di tipo Boolean. Come indicato, il membro m_boxed è privata e volatile, come mostrato in questo frammento:

private volatile object m_boxed;

In C#, la parola chiave volatile indica un membro può essere modificato da un thread in esecuzione contemporaneamente. Per i membri che sono disponibili in un ambiente multithread ma l'assenza (sostanzialmente per motivi di prestazioni) viene utilizzata la parola chiave volatile alcuna protezione contro possibili thread simultanei che può accedere a tali membri nello stesso momento. Possibile restituzione agli aspetti threading di Lazy <T> successivamente. Per ora, basti dire che i membri pubblici e protetti della Lazy <T> sono thread-safe per impostazione predefinita. L'istanza effettiva del tipo T viene creato la prima volta che qualsiasi codice tenta di accedere al membro valore. I dettagli della creazione di oggetti dipendono gli attributi threading eventualmente specificati mediante il costruttore Lazy <T>. Dovrebbe essere evidente che le implicazioni della modalità di threading solo sono importanti quando il valore boxed è effettivamente inizializzato o si accede per la prima volta.

Nel caso predefinito, un'istanza di tipo T viene ottenuta tramite reflection inserendo una chiamata Activator.CreateInstance. Ecco un esempio rapido dell'interazione con il tipo Lazy <T> tipico:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.IsValueCreated);
Console.WriteLine(temp.Value.SomeValue);

Si noti che non è assolutamente necessario controllare IsValueCreated prima di richiamare il valore. In genere ricorrere a controllare il valore di IsValueCreated solo se, per qualsiasi motivo, è necessario sapere se un valore è attualmente associato al tipo lazy. Non è necessario per poter controllare IsValueCreated per evitare un'eccezione di riferimento null Value. Il codice seguente funziona bene:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.Value.SomeValue);

La funzione Get della proprietà Value controlla se esiste già un valore boxed; in caso contrario, attiva la logica che crea un'istanza del tipo il ritorno a capo e che restituisce.

Il processo di creazione di istanze

Naturalmente, se il costruttore di lazy il tipo, ovvero DataContainer nell'esempio precedente, genera un'eccezione, il codice è responsabile della gestione di eccezioni. L'eccezione acquisita è di tipo oggetto TargetInvocationException — l'eccezione tipica durante la reflection di .NET non riesce a creare un'istanza di un tipo indirettamente.

La logica di wrapper <T> lazy assicura semplicemente che viene creata un'istanza di tipo T; in alcun modo viene inoltre inoltre garantisce che non si ottiene un'eccezione di riferimento null come accedere ai membri pubblici in t. Si consideri ad esempio il frammento di codice riportato di seguito:

public class DataContainer
{
  public DataContainer()
  {
  }

  public IList<String> SomeValues { get; set; }
}

Si supponga ora che si tenta di chiamare il codice seguente da un programma client:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.Value.SomeValues.Count);

In questo caso, verrà generata un'eccezione perché la proprietà SomeValues dell'oggetto DataContainer è null, non perché il DataContainer è null se stesso. L'eccezione genera perché costruttore del DataContainer non inizializza correttamente tutti i relativi membri, l'errore ha nulla a che fare con l'implementazione dell'approccio lenta.

La proprietà Value di Lazy <T> è una proprietà di sola lettura, vale a dire che una volta inizializzata, un oggetto Lazy <T> Restituisce sempre la stessa istanza di tipo T o lo stesso valore se T è un tipo di valore. Impossibile modificare l'istanza, ma è possibile accedere a qualsiasi proprietà pubblica che potrebbe essere l'istanza.

Ecco come è possibile configurare un oggetto Lazy <T> passare parametri ad hoc per il tipo T:

temp = new Lazy<DataContainer>(() => new Orders(10));

Uno dei costruttori Lazy <T> accetta un delegato tramite la quale è possibile specificare qualsiasi azione necessaria per generare dati di input appropriati per il costruttore di T. Il delegato non viene eseguito finché non si accede per la prima volta la proprietà Value del tipo T con wrapper.

Inizializzazione thread safe

Per impostazione predefinita, Lazy <T> è thread-safe, ovvero più thread possono accedere a un oggetto e tutti i thread riceverà la stessa istanza
di tipo T. Let’s esaminare aspetti del threading sono importanti solo per il primo accesso a un oggetto lazy.

Il primo thread per raggiungere l'oggetto Lazy <T> avvierà il processo di creazione di un'istanza di tipo t. Tutti i thread nell'accedere al valore di ricevano la risposta generata dal primo, ovvero tutto ciò che è. In altre parole, se il primo thread genera un'eccezione quando si richiama quindi il costruttore del tipo T, tutte le chiamate successive, indipendentemente dal thread, riceverà la stessa eccezione.
Per impostazione predefinita, diversi thread Impossibile ottenere risposte diverse della stessa istanza di Lazy <T>. Questo è il comportamento che quando si sceglie il costruttore predefinito di Lazy <T>.

Classe Lazy <T> funzionalità tuttavia anche un costruttore aggiuntivo:

public Lazy(bool isThreadSafe)

L'argomento booleano indica se non thread-safe. Come accennato, il valore predefinito è true, che offrirà il comportamento citati in precedenza.

Se invece si passa false , accedere alla proprietà Value da un solo thread, ovvero quello che inizializza il tipo lazy. Se più thread tentano di accedere alla proprietà Value, il comportamento è indefinito.

Il costruttore Lazy <T> che accetta un valore booleano è un caso speciale di una firma più generale in cui si passano Lazy <T>
costruttore un valore dall'enumerazione LazyThreadSafetyMode. Figura 1 spiega il ruolo di ciascun valore nell'enumerazione.

Figura 1 di enumerazione TheLazyThreadSafetyMode

Value Descrizione
Nessuna L'istanza Lazy <T> non è thread-safe e relativo comportamento sarà indefinito se vi si accede da più thread.
PublicationOnly Sono consentiti più thread simultaneamente provare a inizializzare il tipo lazy. Il primo thread per completare wins e i risultati generati da tutti gli altri verranno ignorati.
ExecutionAndPublication I blocchi vengono utilizzati per garantire che un solo thread può inizializzare un'istanza Lazy <T> in modo thread-safe.

È possibile impostare la modalità PublicationOnly utilizzando uno dei seguenti costruttori:

public Lazy(LazyThreadSafetyMode mode)
public Lazy<T>(Func<T>, LazyThreadSafetyMode mode)

I valori Figura 1 diverso PublicationOnly sono impostati in modo implicito quando si utilizza il costruttore che accetta un valore booleano:

public Lazy(bool isThreadSafe)

In tale costruttore se isThreadSafe argomento è false, quindi la modalità di threading selezionata è None. Se isThreadSafe l'argomento è impostato su true , la modalità di threading è impostata su ExecutionAndPublication. ExecutionAndPublication è anche la modalità di funzionamento quando si sceglie il costruttore predefinito.

Modalità PublicationOnly se in qualche punto tra il completo thread-safe garantiti da ExecutionAndPublication e l'assenza che si ottiene con nessuno. PublicationOnly consente ai thread concorrenti tentare di creare l'istanza del tipo T, ma garantisce che un solo thread è il vincitore. L'istanza T creato tramite il vincitore è quindi condiviso tra tutti gli altri thread, indipendentemente dall'istanza ciascuno può avere calcolato.

Vi è una differenza tra nessuno interessante e ExecutionAndPublication riguardanti una possibile eccezione generata durante l'inizializzazione. Quando è impostato PublicationOnly, un'eccezione generata durante l'inizializzazione non è memorizzato nella cache; successivamente, ciascun thread tenta di leggere il valore avrà la possibilità di reinizializzare se non è disponibile un'istanza di T. Un'altra differenza tra PublicationOnly e nessuno è che non vengono generate eccezioni in modalità PublicationOnly se il costruttore di T tenta di accedere in modo ricorsivo Value. Tale situazione genererà un'eccezione InvalidOperation quando la classe Lazy <T> funziona in nessuno o modalità ExecutionAndPublication.

Eliminazione thread-safe offre un miglioramento delle prestazioni non elaborati, ma è necessario prestare attenzione evitare errori rischiose e race condition. È consigliabile pertanto che si utilizza l'opzione LazyThreadSafetyMode.None solo quando le prestazioni sono molto importanti.

Se si utilizza LazyThreadSafetyMode.None, resta il compito di assicurare che non verrà inizializzata l'istanza Lazy <T> da più thread. In caso contrario, si possono causare risultati imprevisti. Se viene generata un'eccezione durante l'inizializzazione, la stessa eccezione viene memorizzato nella cache e generata per ogni accesso successivo al valore all'interno del thread stesso.

Inizializzazione ThreadLocal

Per impostazione predefinita, Lazy <T> non ti diversi thread gestire il proprio personale istanza di tipo t. Tuttavia, se si desidera consentire che
comportamento, è necessario optare per una classe diversa, ovvero il tipo ThreadLocal <T>. Ecco come utilizzare:

var counter = new ThreadLocal<Int32>(() => 1);

Il costruttore accetta un delegato e viene utilizzato per inizializzare la variabile locale di thread. Ogni thread contiene i propri dati sono completamente fuori portata di altri thread. A differenza di Lazy <T>, la proprietà Value su ThreadLocal <T> è in lettura / scrittura. Pertanto è indipendente da quella successiva ogni accesso e producano risultati diversi, tra cui generazione (o non) un'eccezione. Se non si fornisce un delegato action tramite il costruttore ThreadLocal <T>, l'oggetto incorporato viene inizializzato con il valore predefinito per il tipo, ovvero null se T è una classe.

Implementazione di proprietà lazy

La maggior parte dei casi, utilizzare Lazy <T> per le proprietà all'interno di classi personalizzate, ma le classi esattamente? ORM strumenti offrono il caricamento lazy autonomamente, in modo che se si utilizzano questi strumenti, accesso ai dati livello probabilmente non segmento dell'applicazione dove troverete probabili candidati alle classi di proprietà lenta dell'host. Se non si utilizzano strumenti ORM, il livello data access è certamente una buona proprietà lenta.

Segmenti dell'applicazione in cui si utilizza l'inserimento delle dipendenze possono essere un altro buon adattamento per pigrizia. In .NET Framework 4, MEF (Managed Extensibility Framework) implementa solo extensibility e inversione del controllo utilizzando Lazy <T>. Anche se non si sta utilizzando direttamente il MEF, la gestione delle dipendenze è un adattamento ottimo per le proprietà lente.

Implementazione di una proprietà all'interno di una classe lazy non richiede Scienza qualsiasi rocket come di Figura 2.

Figura 2 di esempio di una proprietà lazy

public class Customer
{
   private readonly Lazy<IList<Order>> orders;

   public Customer(String id)
   {
      orders = new Lazy<IList<Order>>( () =>
      {
         return new List<Order>();
      }
      );
   }

   public IList<Order> Orders
   {
      get
      {
         // Orders is created on first access
         return orders.Value;
      }
   }
}

Riempire un buco

Il wrapping caricamento lazy è un concetto astratto che fa riferimento al caricamento dei dati solo quando è realmente necessario. Fino a .NET Framework 4, gli sviluppatori necessarie per occuparsi dello sviluppo di logica di inizializzazione differita stessi. La classe Lazy <T> estende il toolkit di programmazione .NET Framework e fornisce possibilità di evitare sprechi calcolo creando oggetti costosi solo quando strettamente necessario e inizia solo qualche istante prima di utilizzarli.

Dino Esposito  è l'autore di Programmazione di ASP.NET MVC Microsoft Press ed è coautore Microsoft. NET: Architettura delle applicazioni per Enterprise (Microsoft Press, 2008). Vive in Italia, Esposito è spesso come relatore a eventi del settore in tutto il mondo. È possibile unire il suo blog all' indirizzo weblogs.asp.net/despos .

Grazie all'esperto di tecnica seguente per la revisione di questo articolo: Greg Paperin