检测应用程序是否空闲
一个工具类,可以在应用程序空闲时向您的代码发出警报。
引言
如果您曾编写过 GUI (WinForms) 应用程序,并希望定期运行后台进程(例如轮询数据库),或者需要在一定时间不活动后注销用户或关闭连接,那么这个类可能适合您。
背景
我创建这个类是为了让我的应用程序能够偶尔检查数据库中是否有其他用户更新的记录。我不想在用户忙于输入数据时运行检查,当然也不想在事务性保存操作的中间访问数据库,以免造成阻塞甚至死锁。因此,我需要知道**用户**和**CPU**何时都处于空闲状态。我还想知道应用程序**何时**开始空闲,以及应用程序**保持**空闲期间的定期更新。
我最终采用了两步检测过程
- 挂钩到
System.Windows.Forms.Application.Idle
事件以**帮助**检测用户何时空闲。请注意,我说了“帮助”…… - 如果用户空闲,则使用
System.Diagnostics.Process.GetCurrentProcess().TotalProcessorTime
来确定**CPU**是否空闲(至少对于此进程而言)。
我还必须添加第三个组件,即一个计时器,以便**继续**检测空闲状态。我发现,只要应用程序具有焦点,它似乎每秒都会获得一个“脉冲”(以 Application.Idle
事件的形式),即使应用程序中没有任何计时器。但是,一旦另一个应用程序获得了焦点,后台应用程序就不再获得该脉冲。如果我仔细查找,我可能会发现 Windows™ 会向拥有焦点的应用程序发送一个系统时钟更新。无论其来源如何,我都需要给该类提供自己的“心跳”,以便它能够继续作为后台应用程序运行。
使用代码
主代码位于静态类 ApplicationIdleTimer
中。
public static event ApplicationIdle;
您通常会使用这个静态事件来监听空闲状态。当应用程序被确定为空闲状态时,您将收到一个包含 ApplicationIdleEventArgs
的事件,该事件会告诉您
DateTime IdleSince
:应用程序开始空闲的时间。TimeSpan IdleDuration
:应用程序空闲的时长。
此外,您还可以查询以下静态类属性来了解
bool IsIdle
:返回**最后确定**的空闲状态。空闲状态每秒重新计算一次。GUI 和 CPU 都必须空闲,此属性才为true
。double CurrentGUIActivity
:返回 GUI 活动的“指示”,以每秒活动量表示。0 表示没有活动。GUI 活动包括用户交互(键入、移动鼠标)以及事件、绘制操作等。double CurrentCPUUsage
:返回当前进程的 CPU 使用率(0.0-1.0)。如果无法确定,将返回double.NaN
。
此外,还有一些可调整的设置,可用于优化应用程序的空闲检测
double GUIActivityThreshold
:用于确定空闲状态的阈值(GUI 活动)。低于此级别的 GUI 活动被视为“空闲”。double CPUUsageThreshold
:用于确定空闲状态的阈值(CPU 使用率)。低于此级别的 CPU 使用率被视为“空闲”。值 >= 1.0 将禁用 CPU 空闲检查。
演示应用程序(如上所示)显示了所有可用的属性,并允许您在运行时调整行为。运行应用程序,只需移动鼠标,状态指示器就应该从“空闲”切换到“忙碌”。增加 GUI 阈值,直到正常鼠标移动不会重置状态。然后单击“执行迭代”按钮执行 CPU 密集型任务,这也会将状态切换为“忙碌”。您也可以调整 CPU 阈值,100% 的阈值将禁用空闲检测中的 CPU 检查。“yield”复选框决定在迭代运行时窗体是否继续处理 GUI 消息。
如果仔细观察,您可能会注意到 GUI 指示器有时会超出其阈值,但类仍然认为应用程序处于空闲状态!这是因为活动可能会偶尔飙升,但阈值是与**每秒平均活动量**进行比较的。因此,只要一秒钟内的**平均**活动量**低于**阈值,应用程序就被视为空闲。CPU 计数器的工作方式**大致相同**,只是其持续时间可能会有所不同(取决于它被调用的频率——或**不频繁**——程度)。
关注点
实际上,此演示并非**最佳**演示,因为所有计时器和频繁的绘制都会干扰空闲检测。向您展示应用程序有多空闲的行为本身就会使其相当忙碌!
观察演示中的 App.Idle
事件计数器。它显示了接收到的 System.Windows.Forms.Application.Idle
事件的总数。您可能希望将其与以下极简代码的显示进行比较
using System;
using System.Windows.Forms;
namespace Demo
{
public class IdleForm : System.Windows.Forms.Form
{
private System.Windows.Forms.Label label1;
private long idleCounter = 0;
public IdleForm()
{
InitializeComponent();
Application.Idle +=
new System.EventHandler(this.Idle_Count);
}
private void InitializeComponent()
{
this.Size = new System.Drawing.Size(300,300);
this.label1 = new System.Windows.Forms.Label();
this.label1.Font =
new System.Drawing.Font("Microsoft Sans Serif", 18F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.label1.Location = new System.Drawing.Point(0, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(300,300);
this.Controls.Add(label1);
}
public static void Main(string[] args)
{
Application.Run(new IdleForm());
}
private void Idle_Count(object sender, System.EventArgs e)
{
idleCounter ++;
label1.Text = idleCounter.ToString();
}
}
}
改进领域
- 由于“空闲”在很大程度上取决于应用程序运行的机器的容量和速度,因此一项改进是让阈值“自动调整”,通过收集几分钟的使用情况统计数据并相应地调整值。
- 如果需要确保**计算机**(而不仅仅是应用程序)处于空闲状态,则可以添加第三个空闲检查。在 1.1 中,只需监视
Process.GetProcessById(0).TotalProcessorTime
即可,它告诉您花费在**系统空闲进程**上的 CPU 时间量。在 2.0 中,我听说这有点困难,因为 Framework 不再允许访问进程 0。