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

Windows服务执行超时致后续日志缺失,求解决方案

问题描述

开发了一款每分钟执行一次的Windows服务,在大数据量测试场景下,TaskRepo.ManageUserAccess().Wait();执行耗时超过1分钟。当下一个定时周期到来时,服务会再次触发任务逻辑,导致该语句后的**"API call completed"日志无法写入文件**。

相关代码如下:

Windows服务类代码

public partial class TaskAccessGrandRevokeService : ServiceBase
{
    Timer timer = new Timer();

    public TaskAccessGrandRevokeService()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        try
        {
            ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; //For SSL
            int interval = Convert.ToInt32(ConfigurationManager.AppSettings["TaskAccessGrandRevokeServiceTimerInterval"]);

            UtilityHelper.WriteTextToFile("\nTaskAccessGrandRevokeService: Service started at " + DateTime.Now);
            timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
            timer.Interval = interval; //time interval in milliseconds (1 minute) 
            timer.Enabled = true;

            OnElapsedTime(null, null);
        }
        catch (Exception ex)
        {
            UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: {DateTime.Now} \nError:{ex.Message}-{ex.StackTrace}");
        }
    }

    protected override void OnStop()
    {
        UtilityHelper.WriteTextToFile("\nTaskAccessGrandRevokeService stopped at " + DateTime.Now);
    }

    private void OnElapsedTime(object source, ElapsedEventArgs e)
    {
        try
        {
            UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API called at {DateTime.Now}");
            TaskRepo.ManageUserAccess().Wait();
            UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API call completed at {DateTime.Now}");
        }
        catch (Exception ex)
        {
            UtilityHelper.WriteTextToFile($"\nYTaskAccessGrandRevokeService: {DateTime.Now} \nError:{ex.InnerException}-{ex.StackTrace}");
        }
    }

}

日志工具类代码

public static class UtilityHelper
{
    public static void WriteTextToFile(string Message)
    {
        string checkPath = AppDomain.CurrentDomain.BaseDirectory + "\\LogsFile";

        if (!Directory.Exists(checkPath))
        {
            Directory.CreateDirectory(checkPath);
        }

        string filepath = AppDomain.CurrentDomain.BaseDirectory + "\\LogsFile\\WindowsServiceLogs_" + DateTime.Now.ToString("MM-dd-yyyy").Replace('-', '_') + ".txt";

        if (!File.Exists(filepath))
        {
            // Create a file to write to.   
            using (StreamWriter sw = File.CreateText(filepath))
            {
                sw.WriteLine(Message);
            }
        }
        else
        {
            using (StreamWriter sw = File.AppendText(filepath))
            {
                sw.WriteLine(Message);
            }

        }
    }
}
解决方案建议

1. 阻止Timer叠加执行

当前使用的System.Timers.Timer会在定时周期到后,直接在新线程触发回调。前一次任务未完成时,新回调会并行执行,导致后续日志被覆盖或无法执行。可以通过两种方式解决:

方式一:执行期间禁用Timer

private void OnElapsedTime(object source, ElapsedEventArgs e)
{
    try
    {
        timer.Enabled = false; // 禁用Timer,避免叠加触发
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API called at {DateTime.Now}");
        TaskRepo.ManageUserAccess().Wait();
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API call completed at {DateTime.Now}");
    }
    catch (Exception ex)
    {
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: {DateTime.Now} \nError:{ex.InnerException}-{ex.StackTrace}");
    }
    finally
    {
        timer.Enabled = true; // 任务完成后重新启用Timer
    }
}

方式二:使用信号量控制并发

private readonly SemaphoreSlim _taskSemaphore = new SemaphoreSlim(1, 1);

private void OnElapsedTime(object source, ElapsedEventArgs e)
{
    // 尝试获取锁,获取不到说明前一次任务还在执行,直接跳过
    if (!_taskSemaphore.Wait(0))
    {
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: Previous task running, skip trigger at {DateTime.Now}");
        return;
    }
    try
    {
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API called at {DateTime.Now}");
        TaskRepo.ManageUserAccess().Wait();
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API call completed at {DateTime.Now}");
    }
    catch (Exception ex)
    {
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: {DateTime.Now} \nError:{ex.InnerException}-{ex.StackTrace}");
    }
    finally
    {
        _taskSemaphore.Release(); // 释放锁
    }
}

2. 用异步/await替代Wait(),避免线程阻塞

Wait()会阻塞当前线程,改成异步回调可以提升线程利用率,同时避免潜在死锁问题:

private readonly SemaphoreSlim _taskSemaphore = new SemaphoreSlim(1, 1);

// 把回调改成异步方法
private async void OnElapsedTime(object source, ElapsedEventArgs e)
{
    if (!_taskSemaphore.Wait(0))
    {
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: Previous task running, skip trigger at {DateTime.Now}");
        return;
    }
    try
    {
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API called at {DateTime.Now}");
        await TaskRepo.ManageUserAccess(); // 使用await替代Wait()
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API call completed at {DateTime.Now}");
    }
    catch (Exception ex)
    {
        UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: {DateTime.Now} \nError:{ex.Message}-{ex.StackTrace}");
    }
    finally
    {
        _taskSemaphore.Release();
    }
}

3. 修复日志写入的线程安全问题

当前日志方法存在多线程写入冲突,可能导致日志丢失或乱序,加锁确保线程安全:

public static class UtilityHelper
{
    private static readonly object _fileLock = new object();

    public static void WriteTextToFile(string Message)
    {
        string logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LogsFile");
        if (!Directory.Exists(logDir))
        {
            Directory.CreateDirectory(logDir);
        }

        string logPath = Path.Combine(logDir, $"WindowsServiceLogs_{DateTime.Now:MM_dd_yyyy}.txt");

        lock (_fileLock) // 加锁保证同一时间只有一个线程写入
        {
            // AppendText会自动创建不存在的文件,无需单独判断
            using (StreamWriter sw = File.AppendText(logPath))
            {
                sw.WriteLine(Message);
            }
        }
    }
}

4. 添加任务超时控制

避免任务无限期执行,给ManageUserAccess添加超时机制:

// 在异步回调中添加超时逻辑
var task = TaskRepo.ManageUserAccess();
var timeoutTask = Task.Delay(TimeSpan.FromMinutes(1.5)); // 设置合理超时时间
var completedTask = await Task.WhenAny(task, timeoutTask);

if (completedTask == timeoutTask)
{
    UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: Task timed out at {DateTime.Now}");
}
else
{
    await task; // 确保任务完成,捕获可能的异常
    UtilityHelper.WriteTextToFile($"\nTaskAccessGrandRevokeService: API call completed at {DateTime.Now}");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 01:05:00