65.9K
CodeProject 正在变化。 阅读更多。
Home

CPUAlert:拯救您的 CPU 免于过热和电池过快耗尽

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (18投票s)

2010年3月2日

CPOL

5分钟阅读

viewsIcon

102404

downloadIcon

1681

CPUAlert 监控进程的 CPU 和内存消耗,并在它们持续占用过多资源时提醒您,并为您提供回收或终止的选项

CPU Alert popup

引言

如果您的计算机运行过热或电池电量消耗过快,那很可能是由于某个应用程序或进程占用了大量CPU或内存。如果您长时间运行应用程序,例如 Outlook,它会持续增加内存消耗并且不会有效释放内存。结果,您的计算机物理内存耗尽,其他应用程序运行缓慢。有时,Outlook、浏览器、图像编辑应用程序或其他一些应用程序会进入某个无限循环,导致它们占用全部CPU,使您的CPU过热,并且您的应用程序性能变得迟钝。

CPUAlert 是一款监视应用程序CPU和内存消耗的应用程序,如果某个应用程序持续占用高CPU或高内存,它会向您发出警报。它不仅能节省您的CPU和电池寿命,还能使您的计算机运行顺畅,让您的活动应用程序运行尽可能快。

如何使用该应用程序

运行时,如果某个进程占用了超过 200 MB 的内存,它会显示一个警报

image

在这里您可以看到我的 Outlook 占用了 244 MB 的物理 RAM。

您可以选择推迟警报 5 分钟(只需按 ESC 键),或永久忽略该进程,这样您就不会再收到该进程的警报,或者您可以关闭它并回收内存。

一个方便的功能是“重启”,它会关闭应用程序并重新启动。这通常会释放被进程占用的内存。

如果某个进程持续占用超过 30% 的 CPU 超过 5 分钟,也会出现相同的警报。

您可以通过右键单击任务栏图标并选择“设置”来配置所有这些设置,例如 CPU 和内存的可接受限度、警报频率、关闭应用程序前的等待时间等。

image

image

该应用程序会注册以在 Windows 启动时启动。如果您想从 Windows 启动中删除它,只需从“开始菜单”->“所有程序”->“启动”菜单中删除应用程序快捷方式即可。

Using the Code

CPU Alert 是一个小项目。重要的文件有

  • MonitorCPUForm.cs:这是应用程序启动时加载的主窗口。它隐藏在系统托盘中,并在后台运行应用程序。它还包含一个“设置”视图,允许您更改 CPU 和内存阈值、警报间隔等。它还协调操作并显示必要的警报。
  • KillProcessForm.cs:它只有上面弹出窗口中显示的警报的用户界面。没有智能。
  • Monitor.cs:这是执行监视、测量 CPU 和内存使用情况并触发警报的重要类。如果您想在您的应用程序中拥有此类功能,可以重用此类。

我们先来看一下 Monitor.cs 类,它具有监视 CPU 和内存的智能。它使用 WMI 来监视进程。我最初尝试使用 Process 类并尝试访问 TotalProcessorTime,但对于 mysqld 等进程,我收到了“访问被拒绝”的错误。对于某些进程,我无法获得 WorkingSet 等信息。然后我尝试使用 PerformanceCounter 来获取进程的 CPU 和内存使用情况。为每个进程查询 Performance Counter 的开销太大了。所以,我不得不依赖 WMI 来提供信息。

private ManagementObjectSearcher _Searcher = 
            new ManagementObjectSearcher("root\\CIMV2",
            "SELECT IDProcess, Name, PercentProcessorTime, Description, 
	   WorkingSet FROM Win32_PerfFormattedData_PerfProc_Process");
.
.
.
private List<ProcessInfo> GetUsage()
{
    var processes = new List<ProcessInfo>();
    foreach (ManagementObject queryObj in _Searcher.Get())
    {
        var process = new ProcessInfo
        {
            Id = Convert.ToInt32(queryObj["IDProcess"]),
            Name = Convert.ToString(queryObj["Name"]),
            CpuUsage = Convert.ToInt32(queryObj["PercentProcessorTime"]),
            Description = Convert.ToString(queryObj["Description"]),
            WorkingSet = Convert.ToInt64(queryObj["WorkingSet"]),
        };

        if (string.IsNullOrEmpty(process.Description))
            process.Title = process.Name;
        else
            process.Title = process.Description;

        if (process.Id > 0)
            processes.Add(process);
    }

    return processes;
}  

非常直接。这里唯一值得学习的酷炫之处是,我将 ManageObjectSearcher 声明为该函数的 `private` 变量,而不是局部变量,因为似乎每次调用 GetUsage 函数时创建和销毁它都非常昂贵。每次我尝试创建和销毁搜索器时,CPU 在 WMIpserv.exe 上会花费大约 30 秒钟占用 20% 的 CPU。

关闭进程成了一个研发挑战。您可以通过调用 Process.Kill 来终止一个进程,但这会异常终止进程。如果您以这种方式终止 Outlook,会导致数据损坏。所以,您必须优雅地关闭应用程序。然而,如果某个应用程序卡住了并占用了 100% 的 CPU,尝试优雅地关闭它不起作用。您必须采取激进措施。在这种情况下,杀死它才是唯一的解决方案。

所以,我首先尝试温和地关闭进程

/// <summary>
/// Closes the process by sending Shutdown message to the main window. 
// Then it starts a timer to check if the process is really closed. 
/// </summary>
/// <param name="processToKill">The process to kill.</param>
private void CloseProcess(ProcessInfo processToKill)
{
    try
    {
        Process process = Process.GetProcessById(processToKill.Id);
        processToKill.Path = process.MainModule.FileName;
        process.CloseMainWindow();
    }
    catch (Exception closeException)
    {
        if (MessageBox.Show(this, "Unable to close process: " 
            + (processToKill.Title) + Environment.NewLine
            + closeException.Message + Environment.NewLine
            + "Do you want to kill it?", "Unable to close process", 
            MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
        {
            KillProcess(processToKill);
        }
    }

    // Check back after a while if the process is still running. 
    //If it's running kill it
    CheckAfterAWhileIfItsStillRunning(processToKill);
} 

这里它调用 Process.CloseMainWindow 函数向进程发送一个关机消息。如果进程有一个可见窗口,它将收到消息并希望它很快关闭。如果没有可见窗口,它将不会收到消息也不会关机。所以,我们必须杀死它。

即使某个进程收到了关机消息,如果它卡在某个无限循环中,也可能无法正确终止。在这种情况下,我们需要稍后检查它是否仍在运行,然后杀死它。

private void KillProcess(ProcessInfo processToKill)
{
try
{
	foreach (Process process in Process.GetProcessesByName(processToKill.Name))
	{
		// Check if the process is still running
		if (process.Id == processToKill.Id)
		{
			if (!process.HasExited)
			{
				process.Kill();
				process.Close();
			}
		}
	}

	// If user has requested the process to be restarted, then restart it.
	if (processToKill.CanRestart)
		Process.Start(processToKill.Path);

}
catch (Exception killException)
{
	MessageBox.Show(this, "Unable to kill process: " 
	+ (processToKill.Title)
	+ Environment.NewLine + "You should restart your computer", 
	"Unable to kill process",
	MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}

这个函数是从一个计时器触发的。我们等待一段时间,然后执行杀死操作。首先检查进程是否还在运行。如果是,则杀死它。

另一个有趣的话题是内存使用。如您所知,Windows Forms 应用程序启动时会占用大约 10 MB 到 15 MB 的内存。对于一个正在后台运行并试图为您节省内存的实用程序来说,这太多了。这是因为当 .NET WinForms 库加载时,它们会加载大量内容,希望您会用到它,从而节省额外的加载时间。所以,我不得不强迫 .NET 清理任何未使用的东西。我有一个 MemoryHelper 来做这件事。

static class MemoryHelper
{
	[DllImport("psapi.dll")]
	static extern int EmptyWorkingSet(IntPtr hwProc);

	public static void ClearMemory()
	{
		try
		{
			GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
		   
			EmptyWorkingSet(Process.GetCurrentProcess().Handle);
		}
		catch
		{
		}
	}
} 

首先它强制进行垃圾回收,然后调用 psapi.dll 上的 EmptyWorkingSet 函数来刷新工作集,即物理内存分配。我不知道它是否有效,但至少任务栏显示该应用程序现在只占用 5 MB 内存。太棒了!

结论

希望 CPUAlert 能帮助您避免 PC 变慢或笔记本电脑电池过快耗尽。如果您觉得它有用,请传播出去。首先给这篇文章投票。

历史

  • 2010 年 3 月 2 日:首次发布

© . All rights reserved.