的问题如何诊断句柄泄漏源
我只是把一些性能记录昨天,我注意到从看任务管理器前一段时间一个句柄泄漏,但修复它一直是低优先级。这是一个每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