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

C# Windows服务Timer触发及OnStop时异步任务无法日志记录求助

问题根源分析

你的问题核心出在**async void的不当使用**以及Windows服务中异步任务的生命周期管理上:

  1. async void的风险:这种异步方法仅适合UI事件处理场景,在Windows服务这类非UI环境中,异常会直接抛到同步上下文(可能导致服务崩溃),且无法跟踪任务的完成状态——服务停止时异步任务可能还没执行完就被终止,Timer触发的任务异常会被静默吞掉,直接导致日志无输出。
  2. Timer回调的异步处理漏洞:System.Timers.Timer的Elapsed事件在线程池线程触发,使用async void时,任务执行完成后没有上下文关联,中途若出现异常会直接丢失,自然看不到日志。
  3. OnStop的同步限制:OnStop是同步方法,调用async void会立即返回,服务可能在异步日志任务完成前就停止,所以停止日志根本没机会写入。
解决方案

1. 重构异步日志方法为async Task

首先把WriteLog改成可等待的异步方法,并添加异常捕获,避免异常静默丢失:

// 用于跟踪所有正在运行的异步日志任务
private readonly ConcurrentBag<Task> _runningTasks = new ConcurrentBag<Task>();

private async Task WriteLogAsync(WorkerLog worker) 
{
    try
    {
        string x = await Logger.WriteLogAsync(worker); 
        Logger.WriteError(x);
    }
    catch (Exception ex)
    {
        // 捕获并记录异常,避免任务失败无痕迹
        Logger.WriteError($"日志写入失败: {ex.Message}\n{ex.StackTrace}");
    }
}

2. 正确处理Timer的异步回调

如果使用System.Timers.Timer,修改Elapsed事件处理程序为async void(这是事件处理的特例),内部调用重构后的异步方法并跟踪任务状态:

private System.Timers.Timer _timer;

protected override void OnStart(string[] args)
{
    // 初始化Timer,30秒触发一次
    _timer = new System.Timers.Timer(30000);
    _timer.Elapsed += async (sender, e) => await HandleTimerElapsed();
    _timer.Start();

    // 同步等待启动日志写入完成,确保启动日志能正常输出
    var startLog = new WorkerLog { /* 填充你的启动日志内容 */ };
    WriteLogAsync(startLog).GetAwaiter().GetResult();
}

private async Task HandleTimerElapsed()
{
    var timerLog = new WorkerLog { /* 填充Timer触发的日志内容 */ };
    var task = WriteLogAsync(timerLog);
    _runningTasks.Add(task);
    // 任务完成后从跟踪集合中移除
    await task.ContinueWith(t => 
    {
        _runningTasks.TryTake(out _);
    }, TaskContinuationOptions.ExecuteSynchronously);
}

3. 让OnStop等待异步任务完成

OnStop需要先停止Timer,再等待所有正在运行的日志任务完成,最后同步写入停止日志:

protected override void OnStop()
{
    // 先停止Timer,避免新的异步任务触发
    _timer?.Stop();
    _timer?.Dispose();

    // 等待所有正在运行的日志任务完成
    var runningTasks = _runningTasks.ToArray();
    if (runningTasks.Length > 0)
    {
        Task.WaitAll(runningTasks);
    }

    // 同步写入停止日志,确保服务停止前日志写完
    var stopLog = new WorkerLog { /* 填充你的停止日志内容 */ };
    WriteLogAsync(stopLog).GetAwaiter().GetResult();
}

额外注意事项

  • Logger的线程安全性:确保Logger.WriteLogAsync和Logger.WriteError是线程安全的,因为Timer回调、OnStart/OnStop会在不同线程调用这些方法,线程不安全可能导致日志丢失或异常。
  • 避免OnStart长时间阻塞:如果WriteLogAsync执行时间很长,GetAwaiter().GetResult()会阻塞OnStart,可能导致服务启动超时。这种情况下可以把启动日志放到后台任务,但要将任务加入_runningTasks跟踪,确保能完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:51:03