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
相关产品推荐
相关产品推荐

