的問題如何診斷句柄泄漏源
我只是把一些性能記錄昨天,我注意到從看任務管理器前一段時間一個句柄泄漏,但修復它一直是低優先級。這是一個每10秒鐘進行一次採樣的過夜運行。
我還沒有運行該故障,由於時間的限制,我的測試計算機也是這樣運行此一邊寫代碼是不理想......所以我不知道我的dev的電腦,如果/當它會崩潰,但我高度懷疑這只是時間問題。
注:的紅色區域盒裝是我「停止」的工作循環,並在短暫的停頓後重新啓動它。線程從〜100下降到了〜20。手柄沒有下降,直到循環在約30秒後重新開始從〜62000至〜40,000。因此,一些把手越來越GC'd,只是沒有那麼多,因爲我想到應該是。我想不出有什麼根防止越來越收集所有這些手柄或他們最初是來自何處(即任務,GUI,文件等)。
如果您已經知道可能導致此問題的原因,則無需進一步閱讀。我已經提供了這個信息和代碼的其餘部分在sussing出來的問題的鳥槍式的方法參考。我將刪除,編輯等,因爲根本原因被縮小。同樣的道理,如果感興趣的東西是缺少讓我知道,我會盡力爲它(日誌,轉儲等)。
我已經完成
在我自己的,我通過這個教程Tracking Handle Misuse走了,並得到儘可能看着轉儲文件找到,其中把手打開和關閉......但是,它的數量太多,難以理解,而且我很難讓符號加載,所以指針對我來說只是一句胡言亂語。
我還沒有經過我的名單上有以下兩個,而是想知道是否有一些友好的方法第一...
- Debug Leaky Apps: Identify And Prevent Memory Leaks In Managed Code
- Tracking down managed memory leaks (how to find a GC leak)
我也將我懷疑這個代碼分解成另一個小應用程序的代碼,並且所有內容似乎都可以毫無問題地收集垃圾(儘管執行模式與真實應用程序相比大大簡化了)。
潛在元兇
我的確有這個最後只要應用是開了好長壽命的實例化類,包括創建只有一次每個然後隱藏/需要5個所示表格。我使用主對象作爲我的應用程序控制器,然後模型和視圖通過事件連接到演示者優先模式中的演示者。
下面是一些事情,我在此應用中,這可能是也可能不是重要的事:
- 使用自定義
Action
,Func
和lambda表達式廣泛,其中一些可能是長壽命 - 3個自定義代表參加事件並可以產生異步執行的
Task
。 - 安全調用
Controls
的擴展。 - 非常,非常大量使用
Task
和Parallel.For
/Parallel.Foreach
運行工法(或事件如上所述) - 不要使用Thread.sleep()方法,而是一個自定義Sleep.For(),它使用一個的AutoResetEvent。
主循環
這個應用程序時,它運行的一般流程是基於環比在離線版本的一系列文件和數字輸入信號的輪詢在在線版本。下面是有關離線版本的註釋的sudo代碼,這是我可以從我的筆記本電腦運行而不需要外部硬件以及上面監視的圖表(我無法訪問硬件在線模式)。
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);
}
異步事件
下面是活動的延伸和大部分使用的是默認選項,異步執行。 'TryRaising()'擴展只是將代理包裝在try-catch中並記錄任何異常(但它們不會重新拋出它不是正常程序流的一部分,因爲它們負責捕獲異常)。
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安全,調用
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;
}
}
}
我只能說「好運」......這是一個艱難的過程,對於一個(志願者)第三方來說,通常很難在這個複雜的事情上取得很大的進步。但是我的同胞們永遠不會驚訝於我。 – Floris
下載Process Explorer並選擇您的應用程序。然後選擇View - > Lower Pane - Handles。這應該給你一個想法,你正在泄漏的處理類型(互斥,事件,文件,...)如果它是一個有名的互斥或文件句柄,你應該有一個很好的機會直接找到問題的來源。 –
@AloisKraus是的,我已經有體育課,只是不知道如何充分使用它。我看到列表中可能有200個句柄,儘管我的應用程序(剛剛重新啓動)現在正在使用約2500個。 〜30'File'和〜40'Key'看起來很穩定,但是很多正在創建和銷燬的Thread線程處理(紅/綠突出顯示被添加和刪除)。列表中只有4個'Event'和6'Mutant'類型。我主要是開火併忘記任務,我不會處理它們或者通常等待。我確實在任務內部捕獲異常,記錄它們,然後返回......讓任務自行減速。 – HodlDwon