2012-05-27 6 views
9

Buona sera a tutti!MakeScreenshot perde?

In un progetto corrente, sto vivendo una perdita di memoria piuttosto preoccupante che non riesco a collegare.

Ho lasciato l'applicazione in esecuzione durante la notte con l'utilizzo standard in corso e quando mi sono svegliato 8 ore più tardi, stava mangiando fino a ~ 750 MB di memoria mentre iniziò a ~ 50 MB. Task Manager di Windows non è adatto per il controllo di perdite diverse dal consentire di scoprire che ne esiste uno in primo luogo.

Ho risolto alcune altre perdite di memoria già, con la principale collegata a Firemonkeys 'TGlowEffect. Non viene rilevato da ReportLeaksOnShutdown ma l'utilizzo della memoria di esso diventa estremamente eccessivo su oggetti modificati dinamicamente (ad esempio, cambi di scala o di scala).

L'ho rintracciato fino a un timer (e disabilitandolo interrompe completamente la perdita), e ho bisogno di assistenza per ripararlo se possibile.

Descrizione: Questo codice utilizza la funzione FireMonkey MakeScreenshot per salvare l'aspetto visivo di un TPanel (SigPanel) ad un TMemoryStream. I dati del flusso vengono quindi caricati su un server FTP remoto utilizzando un codice standard (vedere di seguito). All'interno di SigPanel, ci sono 4 bambini TLabel, 1 TRectangle bambini e 6 TImage bambini.

Note:CfId è una stringa globale e viene generato in base su un extended valore float casuale che viene poi hash con il DateTime in formato yyyymmdd_hhnnsszzz. Questa generazione viene eseguita quando il modulo viene creato e si ripete finché non diventa un valore valido CfId (ovvero non contiene caratteri illegali per l'utilizzo nei nomi file di Windows). Una volta ottenuto un valore valido per CfId, non viene eseguito nuovamente (poiché non è necessario generare un nuovo ID). Questo mi consente di eliminare quasi completamente la possibilità di duplicare CfId.

Il codice nel timer è il seguente;

var 
    i : Integer; 
    SigStream : TMemoryStream; 
begin 
    SigStream := TMemoryStream.Create; 
    SigPanel.MakeScreenshot.SaveToStream(SigStream); 
    SigPanel.MakeScreenshot.Free; 
    if VT2SigUp.Connected then 
    begin 
    VT2SigUp.Put(SigStream,'Sig_'+CfId+'.png',False); 
    end else 
    begin 
    VT2SigUp.Connect; 
    VT2SigUp.Put(SigStream,'Sig_'+CfId+'.png',False); 
    end; 
    SigStream.Free; 
end; 

Con il timer NON esecuzione, le funzioni di codice completamente senza perdite e ReportMemoryLeaksOnShutdown fa NON generare un messaggio. Con il timer abilitato e il permesso di "correre" almeno una volta, sto ottenendo un sacco di perdite che aumenta il numero di volte in cui il timer viene eseguito. Le perdite riportate sono le seguenti;

Small Block Leaks 

1 - 12 Bytes: Unknown x 1 
13 - 20 Bytes: TList x 5, Unknown x 1 
21 - 28 Bytes: TFont x 2, TGradientPoint x 8, TGradientPoints x 4, Unknown x 4 
29 - 36 Bytes: TObjectList<FMX.Types.TCanvasSaveState> x 1, TBrushBitmap x 4, 
TBrushGrab x 4, TPosition x 24, TGradient x 4, UnicodeString x1 
37 - 44 Bytes: TBrushResource x 4 
53 - 60 Bytes: TBrush x 4 
61 - 68 Bytes: TBitmap x 5 
69 - 76 Bytes: TD2DCanvasSaveState x 1 
205 - 220 Bytes: TCanvasD2D x 1 

Sizes of Medium and Large Block Leaks 
200236 

Come viene eseguito il timer, questi valori sono moltiplicati n volte (n essendo il numero di volte in cui il timer è eseguito). I blocchi medi e grandi hanno n del 200236 (ad esempio se il timer ha eseguito 3 volte, è 200236, 200236, 200326).

Di Interesse, se rimuovo il codice associato a MakeScreenshot, la perdita non esiste più e l'utilizzo della memoria rimane a un livello un po 'normale. Oltre al consueto utilizzo della memoria, non c'è nulla fuori dall'ordinario e non vengono segnalate perdite. Ho provato più esempi di codice, sia con il salvataggio in un flusso e il caricamento da lì, o il salvataggio in streaming> File e quindi il caricamento del file, ma sembra esserci una perdita all'interno della funzione stessa.Ho anche aggiunto MakeScreenshot.Free una volta che ho scoperto una perdita qui, ma semplicemente non riesco a collegarlo, e, naturalmente, ho usato try..finally in uno dei miei "test di codice" del mio codice.

Ho persino eseguito il codice con GDI + come tipo di tela e la stessa perdita si verifica lì (con l'unica differenza che il D2D perde riferimento GDI + invece).

Apprezzerei molto qualsiasi ricerca o note che qualcuno ha su questo e, inoltre, una soluzione al problema.

+1

Credo che hai appena trovato una perdita di memoria in FM (: – ComputerSaysNo

+0

Credo che l'impostazione 'ReportMemoryLeaksOnShutdown: = True;' nell'inizializzazione della tua app dovrebbe fare il trucco per mostrarti cosa sta perdendo ... –

+0

@DorinDuminica Credo di sì, ma credo di aver trovato il problema è che il 'Risultato' in' FMX.Types.MakeScreenshot' non è effettivamente liberati. Stanno chiamando semplicemente "Result.Canvas.EndScene' e non lo liberano mai! @Je rryDodge Questo è quello che ho cercato per scoprire cosa stava perdendo esattamente come non sarei stato in grado di ottenere una lista precisa senza di essa :) –

risposta

13

Non si libera la bitmap creata da MakeScreenshot.

procedure TForm1.Button1Click(Sender: TObject); 
var 
    ms: TMemoryStream; 
begin 
    ms := TMemoryStream.Create; 
    Panel1.MakeScreenshot.SaveToStream(ms); 
    ms.Free; 
end; 

Il codice sopra non mantiene un riferimento alla bitmap creata, quindi non ha possibilità di liberarlo. Invece cambiare il vostro disegno come di seguito:

procedure TForm1.Button2Click(Sender: TObject); 
var 
    ms: TMemoryStream; 
    bmp: TBitmap; 
begin 
    ms := TMemoryStream.Create; 
    bmp := Panel1.MakeScreenshot; 
    bmp.SaveToStream(ms); 
    ms.Free; 
    bmp.Free; 
end; 


Con il codice qui sotto si è infatti la creazione di due bitmap e liberando uno di loro.

SigPanel.MakeScreenshot.SaveToStream(SigStream); 
    SigPanel.MakeScreenshot.Free; 


Alla fine, il codice sarebbe più come il qui sotto:

var 
    i : Integer; 
    Bmp: TBitmap; 
    SigStream : TMemoryStream; 
begin 
    SigStream := TMemoryStream.Create; 
    try 
    Bmp := SigPanel.MakeScreenshot; 
    try 
     Bmp.SaveToStream(SigStream); 
     if not VT2SigUp.Connected then 
     VT2SigUp.Connect; 
     VT2SigUp.Put(SigStream, 'Sig_'+CfId+'.png', False); 
    finally 
     Bmp.Free; 
    end; 
    finally 
    SigStream.Free; 
    end; 
end; 
+2

Completando la risposta @Sertac non dimenticare di usare il tentativo per evitare errori in put ed evitare la perdita di memoria in quanto SigStream.Free non sarà chiamato se viene sollevata alcuna eccezione. –

+0

Ah, certo, è tutto. Sembra un modo terribilmente prolisso e inizialmente sembra che il risultato non sia stato liberato in 'FMX.Types'. Ho dato per scontato che 'MakeScreenshot' debba essere chiamato e dato un riferimento (ad esempio un flusso), ma vedere come l'hai fatto mi fa capire come la funzione debba essere usata correttamente. Come ho detto, ho usato 'try..finally' in una delle mie correzioni di codice, ma l'ho tolto per cercare di semplificare il codice il più possibile per risolvere il problema. –

+2

@Scott - Va bene ignorando la gestione degli errori durante la pubblicazione di una domanda qui o durante la progettazione iniziale. Non volevo lasciarlo fuori quando l'ho visto menzionato nei commenti. –