You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中基于System.Timers.Timer的Windows服务:如何检测前一次任务是否仍在运行及获取线程CPU内存占用

解决Windows服务中Timer任务的运行状态判断与资源监控问题

我来帮你拆解这两个常见的Windows服务场景问题,咱们一步步来实现:


一、判断前一次任务是否仍在运行

要避免Timer触发时前一次长任务还没执行完,最稳妥的方式是用线程安全的状态标记来控制,这里有两种常见实现方式:

1. 基于锁和布尔标记(同步任务场景)

适合你的长任务是同步执行的情况,用锁保证状态标记的线程安全:

private System.Timers.Timer _taskTimer;
private readonly object _lockObj = new object();
private bool _isTaskInProgress;

// 服务启动时初始化Timer
public void StartMyService()
{
    int intervalMinutes = X; // 你的间隔分钟数
    _taskTimer = new System.Timers.Timer(intervalMinutes * 60 * 1000);
    _taskTimer.Elapsed += OnTaskTimerElapsed;
    _taskTimer.Start();
}

private void OnTaskTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // 先加锁检查任务状态
    lock (_lockObj)
    {
        if (_isTaskInProgress)
        {
            // 前一次任务还在跑,直接跳过本次触发
            return;
        }
        _isTaskInProgress = true;
    }

    try
    {
        // 执行你的长时间运行操作
        ExecuteLongRunningTask();
    }
    catch (Exception ex)
    {
        // 记得在这里记录异常日志,避免任务崩溃导致状态标记一直为true
        // LogHelper.Error("长任务执行失败", ex);
    }
    finally
    {
        // 无论任务成功/失败,都重置状态标记
        lock (_lockObj)
        {
            _isTaskInProgress = false;
        }
    }
}

private void ExecuteLongRunningTask()
{
    // 你的业务逻辑代码
}

2. 基于SemaphoreSlim(异步任务场景)

如果你的长任务是异步的(用async/await),锁里不能直接await,这时用SemaphoreSlim更合适:

private System.Timers.Timer _taskTimer;
private readonly SemaphoreSlim _taskSemaphore = new SemaphoreSlim(1, 1);

public void StartMyService()
{
    int intervalMinutes = X;
    _taskTimer = new System.Timers.Timer(intervalMinutes * 60 * 1000);
    _taskTimer.Elapsed += OnTaskTimerElapsedAsync;
    _taskTimer.Start();
}

// 注意这里用async void(因为Timer的Elapsed事件是void返回)
private async void OnTaskTimerElapsedAsync(object sender, System.Timers.ElapsedEventArgs e)
{
    // 尝试获取信号量,0毫秒超时表示直接检查是否能进入
    if (!await _taskSemaphore.WaitAsync(0))
    {
        return;
    }

    try
    {
        await ExecuteLongRunningTaskAsync();
    }
    catch (Exception ex)
    {
        // 异常日志记录
    }
    finally
    {
        _taskSemaphore.Release();
    }
}

private async Task ExecuteLongRunningTaskAsync()
{
    // 你的异步业务逻辑
}

二、获取运行中任务线程的CPU和内存资源情况

要追踪线程的资源,首先得记录任务执行的线程ID,然后结合Process类或性能计数器来获取数据:

1. 记录任务线程ID

在任务执行的代码里,实时获取当前运行的线程ID:

private int _currentTaskThreadId;

private void ExecuteLongRunningTask()
{
    // 记录当前线程ID
    _currentTaskThreadId = Thread.CurrentThread.ManagedThreadId;
    // 业务逻辑...
}

// 异步版本
private async Task ExecuteLongRunningTaskAsync()
{
    // 注意:异步任务可能会切换线程,这里记录的是初始执行的线程ID
    _currentTaskThreadId = Thread.CurrentThread.ManagedThreadId;
    // 异步业务逻辑...
}

2. 获取线程CPU使用率

ProcessThread类可以获取线程的累计CPU时间,通过两次采样计算差值得到实时使用率:

private DateTime _lastCpuSampleTime;
private TimeSpan _lastTotalCpuTime;

public double GetCurrentTaskCpuUsage()
{
    lock (_lockObj)
    {
        if (!_isTaskInProgress)
        {
            return 0;
        }
    }

    var currentProcess = Process.GetCurrentProcess();
    // 根据线程ID找到对应的ProcessThread
    var targetThread = currentProcess.Threads.Cast<ProcessThread>()
        .FirstOrDefault(t => t.Id == _currentTaskThreadId);

    if (targetThread == null)
    {
        return 0;
    }

    var currentTime = DateTime.Now;
    var currentTotalCpuTime = targetThread.UserProcessorTime + targetThread.PrivilegedProcessorTime;

    // 第一次采样初始化
    if (_lastCpuSampleTime == default)
    {
        _lastCpuSampleTime = currentTime;
        _lastTotalCpuTime = currentTotalCpuTime;
        return 0;
    }

    // 计算时间差和CPU使用时间差
    double elapsedSeconds = (currentTime - _lastCpuSampleTime).TotalSeconds;
    double cpuUsedSeconds = (currentTotalCpuTime - _lastTotalCpuTime).TotalSeconds;

    // 单个线程的CPU使用率上限是100%(对应一个核心)
    double cpuUsage = Math.Min((cpuUsedSeconds / elapsedSeconds) * 100, 100);

    // 更新采样记录
    _lastCpuSampleTime = currentTime;
    _lastTotalCpuTime = currentTotalCpuTime;

    return cpuUsage;
}

3. 内存资源追踪

线程本身的内存主要是堆栈,很难精确获取实际使用量,通常我们关注以下两类内存:

  • 进程级内存:用Process类获取当前进程的内存使用:
    var currentProcess = Process.GetCurrentProcess();
    long privateMemoryBytes = currentProcess.PrivateMemorySize64; // 私有内存(单位:字节)
    long workingSetBytes = currentProcess.WorkingSet64; // 工作集内存(单位:字节)
    
  • .NET托管堆内存:获取应用程序的托管内存使用:
    long managedHeapMemory = GC.GetTotalMemory(false); // 当前托管堆已分配内存(单位:字节)
    

注意事项

  • 如果你的任务是用线程池线程执行的(比如Task.Run),线程ID可能会被复用,所以最好在任务内部实时记录线程ID,而不是在Timer事件里。
  • 异步任务可能会切换线程,所以线程ID只能追踪初始执行的线程,后续切换的线程无法通过ID关联,这种场景下建议用性能分析工具(如Visual Studio性能探查器)来监控。

内容的提问来源于stack exchange,提问作者enfix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 13:42:38