Il problemaCome diagnosticare fonte di perdita di handle
ho appena messo in qualche registrazione delle prestazioni di ieri, come ho notato un problema di handle di guardare Task Manager po 'di tempo fa, anche se il fissaggio è stato a bassa priorità. Questa è una corsa notturna con un campione ogni 10 secondi.
Non ho ancora eseguito questo al guasto, a causa di vincoli di tempo e il mio computer di test è anche il mio computer di sviluppo in modo da eseguirlo mentre scrivere codice non è l'ideale ... quindi non sono sicuro se/quando lo farà incidente, ma ho il sospetto che sia solo questione di tempo.
Nota: Il rosso inscatolato nella regione è dove "fermato" ciclo di lavoro e riavviato dopo un breve pausa. I thread sono caduti su "stop" da ~ 100 a ~ 20. The Handles non è caduto finché il loop non è stato riavviato dopo circa 30 secondi da ~ 62.000 a ~ 40.000. Quindi alcune maniglie stanno ricevendo GC, solo non quasi il numero che mi aspetto dovrebbe essere. Non riesco a capire quale sia la radice che impedisce a tutti questi handle di essere raccolti o da dove provengono (ad esempio, attività, GUI, file ecc.).
Se hai già un'idea di cosa potrebbe causare questo problema, non è necessario leggere più oltre. Ho fornito il resto di queste informazioni e il codice di riferimento in un approccio stile colpo di pistola per scoprire il problema. Rimuoverò, modificherò, ecc. Poiché la causa principale è ridotta. Per lo stesso motivo, se manca qualcosa di interessante fammelo sapere e cercherò di fornirlo (log, discariche, ecc.).
quello che ho fatto
Sul mio Ho passato attraverso questo tutorial su Tracking Handle Misuse e ottenuto per quanto riguarda guardare i file di dump per trovare dove le maniglie Apri e Chiudi ... comunque era troppo opprimente con migliaia di maniglie per avere un senso e ho avuto problemi a caricare i simboli in modo che i puntatori fossero semplicemente incomprensibili per me.
Devo ancora passare attraverso i seguenti due sulla mia lista, ma si chiedeva se ci sono stati alcuni metodi amichevoli prima ...
- Debug Leaky Apps: Identify And Prevent Memory Leaks In Managed Code
- Tracking down managed memory leaks (how to find a GC leak)
Ho anche Suddiviso il codice che sospettavo essere le potenziali cause di ciò in un'altra piccola applicazione e tutto appariva per ottenere Garbage Collected senza problemi (anche se il modello di esecuzione era notevolmente semplificato rispetto alla vera app).
potenziali colpevoli
Io ho diverse classi istanziate lunga durata che durano fino a quando l'applicazione è aperta per, tra cui 5 moduli creati solo una volta e poi mostrate/nascoste, se necessario. Io uso un oggetto principale come controller dell'applicazione e quindi Modelli e viste sono cablati tramite eventi ai Presentatori in uno schema di Presenter-First.
Qui di seguito sono alcune cose che faccio in questa applicazione, che può o non può essere importante:
- Utilizzare personalizzati
Action
,Func
e lambda estesamente, alcuni dei quali possono essere longevo - 3 personalizzato delegati per eventi e che possono generare spa
Task
s per l'esecuzione asincrona. - Estensione per invocazione sicura su
Controls
. - Molto, molto pesantemente utilizzare
Task
eParallel.For
/Parallel.Foreach
per eseguire i metodi di lavoro (o eventi come detto sopra) - Non usare mai Thread.Sleep(), ma invece un costume Sleep.For(), che utilizza un AutoResetEvent.
principale Loop
Il flusso generale di questa applicazione quando è esecuzione si basa su un loop su una serie di file nella Offline versione e l'interrogazione di un segnale di ingresso digitale nella versione online. Di seguito è riportato il codice sudo con i commenti per la versione Offline che è ciò che posso eseguire dal mio computer portatile senza la necessità di hardware esterno e quello che il grafico sopra stava monitorando (non ho accesso all'hardware per Online modalità in questo momento).
public void foo()
{
// Sudo Code
var InfiniteReplay = true;
var Stopped = new CancellationToken();
var FileList = new List<string>();
var AutoMode = new ManualResetEvent(false);
var CompleteSignal = new ManualResetEvent(false);
Action<CancellationToken> PauseIfRequired = (tkn) => { };
// Enumerate a Directory...
// ... Load each file and do work
do
{
foreach (var File in FileList)
{
/// Method stops the loop waiting on a local AutoResetEvent
/// if the CompleteSignal returns faster than the
/// desired working rate of ~2 seconds
PauseIfRequired(Stopped);
/// While not 'Stopped', poll for Automatic Mode
/// NOTE: This mimics how the online system polls a digital
/// input instead of a ManualResetEvent.
while (!Stopped.IsCancellationRequested)
{
if (AutoMode.WaitOne(100))
{
/// Class level Field as the Interface did not allow
/// for passing the string with the event below
m_nextFile = File;
// Raises Event async using Task.Factory.StartNew() extension
m_acquireData.Raise();
break;
}
}
// Escape if Canceled
if (Stopped.IsCancellationRequested)
break;
// If In Automatic Mode, Wait for Complete Signal
if (AutoMode.WaitOne(0))
{
// Ensure Signal Transition
CompleteSignal.WaitOne(0);
if (!CompleteSignal.WaitOne(10000))
{
// Log timeout and warn User after 10 seconds, then continue looping
}
}
}
// Keep looping through same set of files until 'Stopped' if in Infinite Replay Mode
} while (!Stopped.IsCancellationRequested && InfiniteReplay);
}
Async Eventi
Di seguito è riportato l'estensione per gli eventi e la maggior parte vengono eseguiti utilizzando l'opzione di default asincrono. Le estensioni 'TryRaising()' racchiudono i delegati in un try-catch e registrano eventuali eccezioni (mentre non rilanciano non fanno parte del normale flusso di programma perché siano responsabili della cattura delle eccezioni).
using System.Threading.Tasks;
using System;
namespace Common.EventDelegates
{
public delegate void TriggerEvent();
public delegate void ValueEvent<T>(T p_value) where T : struct;
public delegate void ReferenceEvent<T>(T p_reference);
public static partial class DelegateExtensions
{
public static void Raise(this TriggerEvent p_response, bool p_synchronized = false)
{
if (p_response == null)
return;
if (!p_synchronized)
Task.Factory.StartNew(() => { p_response.TryRaising(); });
else
p_response.TryRaising();
}
public static void Broadcast<T>(this ValueEvent<T> p_response, T p_value, bool p_synchronized = false)
where T : struct
{
if (p_response == null)
return;
if (!p_synchronized)
Task.Factory.StartNew(() => { p_response.TryBroadcasting(p_value); });
else
p_response.TryBroadcasting(p_value);
}
public static void Send<T>(this ReferenceEvent<T> p_response, T p_reference, bool p_synchronized = false)
where T : class
{
if (p_response == null)
return;
if (!p_synchronized)
Task.Factory.StartNew(() => { p_response.TrySending(p_reference); });
else
p_response.TrySending(p_reference);
}
}
}
GUI Safe-Invoke
using System;
using System.Windows.Forms;
using Common.FluentValidation;
using Common.Environment;
namespace Common.Extensions
{
public static class InvokeExtensions
{
/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// http://stackoverflow.com/q/714666
public static void SafeInvoke(this Control p_control, Action p_action, bool p_forceSynchronous = false)
{
p_control
.CannotBeNull("p_control");
if (p_control.InvokeRequired)
{
if (p_forceSynchronous)
p_control.Invoke((Action)delegate { SafeInvoke(p_control, p_action, p_forceSynchronous); });
else
p_control.BeginInvoke((Action)delegate { SafeInvoke(p_control, p_action, p_forceSynchronous); });
}
else
{
if (!p_control.IsHandleCreated)
{
// The user is responsible for ensuring that the control has a valid handle
throw
new
InvalidOperationException("SafeInvoke on \"" + p_control.Name + "\" failed because the control had no handle.");
/// jwdebug
/// Only manually create handles when knowingly on the GUI thread
/// Add the line below to generate a handle http://stackoverflow.com/a/3289692/1718702
//var h = this.Handle;
}
if (p_control.IsDisposed)
throw
new
ObjectDisposedException("Control is already disposed.");
p_action.Invoke();
}
}
}
}
Sleep.For()
using System.Threading;
using Common.FluentValidation;
namespace Common.Environment
{
public static partial class Sleep
{
public static bool For(int p_milliseconds, CancellationToken p_cancelToken = default(CancellationToken))
{
// Used as "No-Op" during debug
if (p_milliseconds == 0)
return false;
// Validate
p_milliseconds
.MustBeEqualOrAbove(0, "p_milliseconds");
// Exit immediate if cancelled
if (p_cancelToken != default(CancellationToken))
if (p_cancelToken.IsCancellationRequested)
return true;
var SleepTimer =
new AutoResetEvent(false);
// Cancellation Callback Action
if (p_cancelToken != default(CancellationToken))
p_cancelToken
.Register(() => SleepTimer.Set());
// Block on SleepTimer
var Canceled = SleepTimer.WaitOne(p_milliseconds);
return Canceled;
}
}
}
Tutto quello che posso dire è "buona fortuna" ... Questo è difficile, e di solito è molto difficile per un terzo (volontario) fare molti progressi in qualcosa di così complesso. Ma i miei compagni SO non cessano mai di stupirmi. – Floris
Scarica Process Explorer e seleziona la tua app. Quindi selezionare sotto Visualizza -> riquadro inferiore - maniglie. Questo dovrebbe darti un'idea del tipo di handle che stai perdendo (Mutex, Event, File, ...) Se si tratta di un mutex o di un handle di file, dovresti avere una buona possibilità di trovare direttamente da dove provengono i problemi. –
@AloisKraus Sì, ho già PE, ma non so come usarlo in tutta la sua estensione. Vedo forse 200 maniglie nell'elenco, sebbene la mia app (appena riavviata) stia usando circa ~ 2500 in questo momento. ~ 30 'File' e ~ 40' Key' che sembrano entrambi stabili, ma molte maniglie 'Thread' che vengono create e distrutte (le evidenziazioni rosso/verde vengono aggiunte e rimosse). Solo 4 tipi 'Event' e 6' Mutant' nell'elenco. Per lo più fuoco e dimentico le attività, non le dispongo o di solito aspetto. Rilevo le eccezioni all'interno di Google Task, le registro e poi le restituisco ... lasciando che il compito giri su se stesso. – HodlDwon