Antenka 将您带向了一个好的方向。您不应该在每次更新/请求价值时处理和重新创建性能计数器。实例化性能计数器是有成本的,并且第一次读取可能不准确,如下面的引用所示。此外,您的 lock() { ... } 陈述非常广泛(它们涵盖了很多陈述)并且会很慢。最好让你的锁尽可能小。我要对 Antenka 的质量参考和良好建议进行投票!
但是,我想我可以为您提供更好的答案。我在监控服务器性能方面有相当多的经验,并且确切地了解您的需求。您的代码没有考虑到的一个问题是,任何显示性能计数器的代码(.aspx、.asmx、控制台应用程序、winform 应用程序等)都可能以任何速度请求此统计信息;它可以每 10 秒请求一次,也许每秒 5 次,你不知道也不应该关心。因此,您需要将 PerformanceCounter 集合代码与实际报告当前 Requests / Second 值的代码分开。出于性能原因,我还将向您展示如何在第一次请求时设置性能计数器,然后继续运行,直到 5 秒内没有人发出任何请求,然后正确关闭/处置 PerformanceCounter。
public class RequestsPerSecondCollector
{
#region General Declaration
//Static Stuff for the polling timer
private static System.Threading.Timer pollingTimer;
private static int stateCounter = 0;
private static int lockTimerCounter = 0;
//Instance Stuff for our performance counter
private static System.Diagnostics.PerformanceCounter pcReqsPerSec;
private readonly static object threadLock = new object();
private static decimal CurrentRequestsPerSecondValue;
private static int LastRequestTicks;
#endregion
#region Singleton Implementation
/// <summary>
/// Static members are 'eagerly initialized', that is,
/// immediately when class is loaded for the first time.
/// .NET guarantees thread safety for static initialization.
/// </summary>
private static readonly RequestsPerSecondCollector _instance = new RequestsPerSecondCollector();
#endregion
#region Constructor/Finalizer
/// <summary>
/// Private constructor for static singleton instance construction, you won't be able to instantiate this class outside of itself.
/// </summary>
private RequestsPerSecondCollector()
{
LastRequestTicks = System.Environment.TickCount;
// Start things up by making the first request.
GetRequestsPerSecond();
}
#endregion
#region Getter for current requests per second measure
public static decimal GetRequestsPerSecond()
{
if (pollingTimer == null)
{
Console.WriteLine("Starting Poll Timer");
// Let's check the performance counter every 1 second, and don't do the first time until after 1 second.
pollingTimer = new System.Threading.Timer(OnTimerCallback, null, 1000, 1000);
// The first read from a performance counter is notoriously inaccurate, so
OnTimerCallback(null);
}
LastRequestTicks = System.Environment.TickCount;
lock (threadLock)
{
return CurrentRequestsPerSecondValue;
}
}
#endregion
#region Polling Timer
static void OnTimerCallback(object state)
{
if (System.Threading.Interlocked.CompareExchange(ref lockTimerCounter, 1, 0) == 0)
{
if (pcReqsPerSec == null)
pcReqsPerSec = new System.Diagnostics.PerformanceCounter("W3SVC_W3WP", "Requests / Sec", "_Total", true);
if (pcReqsPerSec != null)
{
try
{
lock (threadLock)
{
CurrentRequestsPerSecondValue = Convert.ToDecimal(pcReqsPerSec.NextValue().ToString("N2"));
}
}
catch (Exception) {
// We had problem, just get rid of the performance counter and we'll rebuild it next revision
if (pcReqsPerSec != null)
{
pcReqsPerSec.Close();
pcReqsPerSec.Dispose();
pcReqsPerSec = null;
}
}
}
stateCounter++;
//Check every 5 seconds or so if anybody is still monitoring the server PerformanceCounter, if not shut down our PerformanceCounter
if (stateCounter % 5 == 0)
{
if (System.Environment.TickCount - LastRequestTicks > 5000)
{
Console.WriteLine("Stopping Poll Timer");
pollingTimer.Dispose();
pollingTimer = null;
if (pcReqsPerSec != null)
{
pcReqsPerSec.Close();
pcReqsPerSec.Dispose();
pcReqsPerSec = null;
}
}
}
System.Threading.Interlocked.Add(ref lockTimerCounter, -1);
}
}
#endregion
}
现在来解释一下。
- 首先你会注意到这个类被设计成一个静态单例。
你不能加载它的多个副本,它有一个私有构造函数
并且急切地初始化了它自己的内部实例。这使得
确保您不会意外创建相同的多个副本
PerformanceCounter。
- 接下来你会注意到私有构造函数(这只会运行
首次访问该类时)我们同时创建
PerformanceCounter 和一个用于轮询
PerformanceCounter。
- Timer 的回调方法将创建
PerformanceCounter if
需要并获得它的下一个值是可用的。也是每5次迭代
我们要看看自您上次请求
PerformanceCounter 的价值。如果超过 5 秒,我们将
目前不需要关闭轮询计时器。我们可以
如果我们再次需要它,请稍后再启动它。
- 现在我们有一个名为
GetRequestsPerSecond() 的静态方法供您使用
调用将返回 RequestsPerSecond 的当前值
PerformanceCounter。
此实现的好处是您只需创建一次性能计数器,然后继续使用直到完成。它易于使用,因为您可以从任何需要它的地方(.aspx、.asmx、控制台应用程序、winforms 应用程序等)简单地调用RequestsPerSecondCollector.GetRequestsPerSecond()。始终只有一个PerformanceCounter,并且无论您调用RequestsPerSecondCollector.GetRequestsPerSecond() 的速度有多快,它都将始终以每秒1 次的频率进行轮询。如果您在超过 5 秒内没有请求它的值,它也会自动关闭并处理 PerformanceCounter。当然,您可以调整计时器间隔和超时毫秒以满足您的需要。您可以更快地轮询并在 60 秒而不是 5 秒内超时。我选择了 5 秒,因为它证明它在 Visual Studio 中调试时工作得非常快。一旦你测试它并知道它可以工作,你可能需要更长的超时时间。
希望这不仅可以帮助您更好地使用 PerformanceCounters,而且还可以安全地重用这个类,它与您想在其中显示统计信息的任何内容分开。可重用的代码总是一个加分项!
编辑:作为后续问题,如果您想在此性能计数器运行时每 60 秒执行一次清理或保姆任务怎么办?好吧,我们已经让计时器每 1 秒运行一次,并且有一个名为 stateCounter 的变量跟踪我们的循环迭代,该变量在每次计时器回调时递增。所以你可以像这样添加一些代码:
// Every 60 seconds I want to close/dispose my PerformanceCounter
if (stateCounter % 60 == 0)
{
if (pcReqsPerSec != null)
{
pcReqsPerSec.Close();
pcReqsPerSec.Dispose();
pcReqsPerSec = null;
}
}
我应该指出,示例中的这个性能计数器不应该“过时”。我认为“请求/秒”应该是一个平均值,而不是一个移动平均统计数据。但这个示例只是说明了您可以的一种方式> 定期对您的PerformanceCounter 进行任何类型的清理或“保姆”。在这种情况下,我们将关闭并处理性能计数器,这将导致它在下次计时器回调时重新创建。您可以根据您的用例并根据您正在使用的特定 PerformanceCounter 进行修改。大多数阅读此问题/答案的人不需要这样做。检查所需 PerformanceCounter 的文档以查看它是连续计数、平均值还是移动平均等...并适当调整您的实施。