2011-09-27 14 views
45

Recentemente ho iniziato a utilizzare Lazy in tutta la mia applicazione e mi chiedevo se ci sono ovvi aspetti negativi di cui devo tenere conto quando uso Lazy<T>?Svantaggi di Lazy <T>?

Sto cercando di utilizzare Lazy<T> tutte le volte che lo ritengo opportuno, principalmente per ridurre l'ingombro di memoria dei plug-in caricati ma inattivi.

+4

Ho appena iniziato a utilizzare Lazy e trovo che sia spesso indicativo di una progettazione errata; o pigrizia da parte del programmatore. Inoltre, uno svantaggio è che devi essere più vigile con le variabili scoped up e creare chiusure appropriate. – Gleno

+4

@Gleno Perché esattamente la pigrizia di questo programmatore? –

+4

@Gleno, Anton: E, ancora più importante, perché è male? Insegno sempre nei miei corsi di programmazione che la pigrizia è una virtù importante nei programmatori. –

risposta

17

sarò espandere un po 'sul mio commento, che recita:

Ho appena iniziato a utilizzare pigro, e scoprire che è spesso indicativa di cattiva progettazione; o pigrizia da parte del programmatore. Inoltre, uno svantaggio è che devi essere più vigile con le variabili e creare le chiusure appropriate.

Per esempio, ho usato Lazy<T> per creare le pagine che l'utente può vedere nel mio (sessionless) MVC app. È un wizard guidato, quindi l'utente potrebbe voler passare a un passaggio casuale precedente. Quando viene eseguita l'handshake, viene creato un array di oggetti Lazy<Page> e, se l'utente specifica come passo, viene valutata la pagina esatta. Trovo offre buone prestazioni, ma ci sono alcuni aspetti ad esso che non mi piacciono, ad esempio, molti dei miei foreach costrutti ora apparire così:

foreach(var something in somethings){ 
    var somethingClosure = something; 
    list.Add(new Lazy<Page>(() => new Page(somethingClosure)); 
} 

Vale a dire devi affrontare il problema delle chiusure in modo molto proattivo. Altrimenti, non penso che sia una cattiva prestazione per immagazzinare un lambda e valutarlo quando necessario.

D'altra parte questo potrebbe essere indicativo che il programmatore sia un Lazy<Programmer>, nel senso che preferiresti non pensare al tuo programma ora, e invece lasciare che la logica corretta valuti quando necessario, come con esempio nel mio caso - invece di costruire quell'array, ho potuto capire solo quale sarebbe stata la specifica pagina richiesta; ma ho scelto di essere pigro e fare un approccio tutto in.

EDIT

E viene in mente che Lazy<T> ha anche un paio di peculiars quando si lavora con la concorrenza. Ad esempio c'è un ThreadLocal<T> per alcuni scenari e diverse configurazioni di flag per il tuo particolare scenario multi-thread. Puoi leggere di più su msdn.

+3

Questo non è un problema di 'Lazy ' di per sé. Piuttosto, è come lo si usa. –

+3

@Anton, sì; e la mia congettura è che Lazy <> è * a volte * problematico nel darti la possibilità di non cercare una soluzione migliore. Con questa opzione potresti accontentarti di qualcosa che funziona e basta. – Gleno

+0

@Fuji, diciamo così: il peggio che può accadere è che all'improvviso dovresti valutare tutti i tuoi oggetti Lazy a causa del cambiamento delle specifiche o di una riscrittura importante. Puoi vivere con quello? – Gleno

4

Come con qualsiasi cosa, Lazy<T> può essere utilizzato per il bene o per il male, quindi uno svantaggio: se usato in modo inappropriato, può causare confusione e frustrazione. Tuttavia, il modello di inizializzazione pigro esiste da anni, e ora .NET BCL ha uno sviluppatore di implementazione che non ha bisogno di reinventare nuovamente la ruota. Cosa c'è di più, MEF loves Lazy.

2

Cosa intendete esattamente con "nella mia domanda"?

Penso che dovrebbe essere usato solo quando non si è sicuri se il valore verrà usato o meno, il che può essere solo il caso con parametri facoltativi che impiegano molto tempo per il calcolo. Ciò potrebbe includere calcoli complessi, gestione di file, servizi Web, accesso al database e così via.

D'altra parte, perché utilizzare Lazy qui? Nella maggior parte dei casi puoi semplicemente chiamare un metodo invece di lazy.Value e non fa comunque differenza. MA è più semplice e ovvio per il programmatore cosa sta succedendo in questa situazione senza Lazy.

Uno a testa ovvia può essere implementato già nella cache del valore, ma non credo che questo è un grande vantaggio tale.

7

Qui non è un aspetto abbastanza negativo, ma un trucco per i pigri :).

Gli inizializzatori pigri sono come inizializzatori statici. Corrono una volta. Se viene generata un'eccezione, l'eccezione viene memorizzata nella cache e le successive chiamate a .Value generano la stessa eccezione. Questo è di progettazione ed è menzionato nella documentazione ... http://msdn.microsoft.com/en-us/library/dd642329.aspx:

Le eccezioni generate da valueFactory vengono memorizzate nella cache.

Quindi, codice come di seguito non potrà mai restituire un valore:

bool firstTime = true; 
Lazy<int> lazyInt = new Lazy<int>(() => 
{ 
    if (firstTime) 
    { 
     firstTime = false; 
     throw new Exception("Always throws exception the very first time."); 
    } 

    return 21; 
}); 

int? val = null; 
while (val == null) 
{ 
    try 
    { 
     val = lazyInt.Value; 
    } 
    catch 
    { 

    } 
} 
+0

Grazie a @Thilak. Questo è interessante. Non ho idea che le eccezioni siano state memorizzate nella cache in questo modo. – eandersson

+1

@Fuji Ecco perché il team MEF ha aggiunto ExportFactory ed ExportLifetimeContext in MEF 2. Dai un'occhiata a http://blogs.msdn.com/b/bclteam/archive/2011/11/17/exportfactory-amp-lt-t -amp-gt-in-mef-2-alok.aspx –

+0

Penso di dover tornare indietro e rivedere parte del mio codice MEF ora.;) – eandersson

7

A mio parere, si dovrebbe sempre avere una ragione per scegliere pigro. Ci sono diverse alternative a seconda del caso d'uso e ci sono sicuramente casi in cui questa struttura è adeguata. Ma non usarlo solo perché è bello.

Per esempio io non ottengono il punto nell'esempio selezione della pagina in una delle altre risposte. Utilizzando un elenco di Lazy per la selezione di un singolo elemento può essere ben fatto con una lista o un dizionario di delegati direttamente senza utilizzare pigro o con un semplice switch.

alternative Così il più evidenti sono

  • esemplificazione diretta per strutture di dati a basso costo o strutture che sono comunque necessari
  • delegati per le cose che sono necessarie zero a poche volte in qualche algoritmo
  • qualche struttura di caching per gli articoli che dovrebbe liberare la memoria quando non viene utilizzato per qualche tempo
  • qualche tipo di struttura "futuro" come il Task che già può iniziare l'inizializzazione in modo asincrono prima di utilizzo effettivo in termini di tempo di inattività della CPU in casi DOVE e la probabilità è piuttosto elevata che la struttura sarà necessario successivamente

In contrasto a ciò, artificiale è spesso adatto quando sono necessarie

  • computazionalmente intense strutture dati
  • zero a molte volte in alcuni algoritmi in cui il caso zero ha una probabilità significativa
  • e i dati sono locali per qualche metodo o classe e possono essere raccolti inutilmente quando non sono più in uso o i dati devono essere conservati per l'intero runtime del programma
5

Sono venuto a utilizzare Lazy<T> principalmente a causa delle sue capacità di concorrenza durante il caricamento delle risorse dal database. Così mi sono sbarazzato degli oggetti di blocco e degli schemi di bloccaggio discutibili. Nel mio caso ConcurrentDictionary + Lazy come valore fatto il mio giorno, grazie alla @Reed Copsey e il suo blog post

Questo appare simile alla seguente. Invece di chiamare:

MyValue value = dictionary.GetOrAdd(
          key, 
          () => new MyValue(key)); 

Vorremmo invece utilizzare un ConcurrentDictionary>, e scrittura:

MyValue value = dictionary.GetOrAdd(
          key, 
          () => new Lazy<MyValue>(
           () => new MyValue(key))) 
          .Value; 

Nessun aspetti negativi di Lazy<T> notato finora.

+0

È molto carino. Purtroppo sono stato un po 'troppo attivo oggi e ho esaurito i voti, quindi non posso votare la tua risposta. ; * ( – eandersson

2

Pigro è utilizzato per preservare le risorse mentre non è realmente necessario. Questo schema è abbastanza buono ma l'implementazione può essere inutile.

Più grande è la risorsa, utile è questo modello.

Un errore nell'utilizzo della classe Lazy è la non trasparenza dell'utilizzo. In effetti, devi mantenere ovunque un'indirizzamento aggiuntivo (.Value). Quando hai solo bisogno di un'istanza di tipo reale, è costretto a caricare anche se non hai bisogno di usarlo direttamente.

Pigro è per lo sviluppo pigro che guadagna produttività ma questo guadagno può essere perso con un uso elevato.

Se si dispone di un'implementazione trasparente reale (utilizzando un modello proxy per esempio) si elimina il disavaggio e può essere molto utile in molti casi.

La concorrenza deve essere considerata in un altro aspetto e non implementata di default nel tuo tipo. Deve essere incluso solo nel codice client o nel tipo di aiuto per questo concetto.