.NET Windows服务调用本地API时出现空引用异常求助
.NET Windows服务调用本地API空引用异常问题修复
问题描述
创建了一个.NET Windows服务,尝试调用本地API(http://localhost:7113/Home/SincronizarDatos),但无法验证API返回值并写入Txt日志,运行时抛出NullReferenceException。相同逻辑用计划任务执行时能正常获取预期返回值。日志显示RestClient、RestRequest实例被错误判定为创建失败,最终在执行response.Content.ToString()时触发空引用异常。
相关代码
Program.cs
namespace MyNewService { internal static class Program { static void Main() { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun); } } }
MyNewService.cs
namespace MyNewService { public partial class Service1: ServiceBase { int tiempo = 5;// Settings1.Default.Tiempo; public Thread worker = null; public Service1() { InitializeComponent(); } protected override void OnStart(string[] args) { try { AgregaLog("Inicio el servicio", "OnStart"); ThreadStart star = new ThreadStart(working); worker = new Thread(star); worker.Start(); } catch (Exception ex) { AgregaLog("Ocurrio un problema:" + ex.Message, "OnStart"); throw; } } protected override void OnStop() { try { if (worker != null && worker.IsAlive) { worker.Abort(); } } catch (Exception ex) { AgregaLog("Ocurrio un problema:" + ex.Message, "OnStop"); throw; } } public void working() { int i = 0; while (true) { AgregaLog("Ejecutando linea " + i, "working"); i++; ejecutarWS(); Thread.Sleep(tiempo * 60 * 100); } } public void ejecutarWS() { try { AgregaLog("ejecutar WS", "ejecutarWS"); var client = new RestClient(); if (client != null) { AgregaLog("RestClient fallo", "ejecutarWS"); } var request = new RestRequest("http://localhost:7113/Home/SincronizarDatos"); request.Method = Method.Post; if (request != null) { AgregaLog("request fallo", "ejecutarWS"); } var response = client.Execute(request); if (response != null) { AgregaLog("response fallo", "ejecutarWS"); } AgregaLog("resultado" + response.Content.ToString(), "ejecutarWS"); } catch (Exception ex) { AgregaLog("Ocurrio el problema:" + ex+"\n", "OnStop"); throw; } } public void AgregaLog(string Valor, string Metodo) { StreamWriter log; FileStream fileStream = null; DirectoryInfo logDirInfo = null; FileInfo logFileInfo; string logFilePath = "C:\\Logs_Servicio_TankFarm_Users\\"; try { if (!Directory.Exists(logFilePath)) { Directory.CreateDirectory(logFilePath); } } catch (Exception ex) { // handle them here } logFilePath = logFilePath + "Log-" + System.DateTime.Today.ToString("[dd-MM-yyyy HH:mm]") + "." + "txt"; logFileInfo = new FileInfo(logFilePath); logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName); if (!logDirInfo.Exists) logDirInfo.Create(); if (!logFileInfo.Exists) { fileStream = logFileInfo.Create(); } else { fileStream = new FileStream(logFilePath, FileMode.Append); } log = new StreamWriter(fileStream); log.WriteLine(System.DateTime.Today.ToString("[dd-MM-yyyy HH:mm]") + "-" + "METODO: " + Metodo + "-- Desc: " + Valor); log.Close(); } } }
日志内容
[17-04-2023 00:00]-METODO: OnStart-- Desc: Inicio el servicio [17-04-2023 00:00]-METODO: working-- Desc: Ejecutando linea 0 [17-04-2023 00:00]-METODO: ejecutarWS-- Desc: ejecutar WS [17-04-2023 00:00]-METODO: ejecutarWS-- Desc: RestClient fallo [17-04-2023 00:00]-METODO: ejecutarWS-- Desc: request fallo [17-04-2023 00:00]-METODO: ejecutarWS-- Desc: response fallo [17-04-2023 00:00]-METODO: OnStop-- Desc: Ocurrio el problema:System.NullReferenceException: Referencia a objeto no establecida como instancia de un objeto. en MyNewService.Service1.ejecutarWS() en C:\Users\leona\source\repos\MyNewService\MyNewService\MyNewService .cs:línea 104
问题根源分析
- 日志判断逻辑完全颠倒:代码中
if (client != null)实际是判断实例创建成功,却错误记录为"RestClient fallo",导致日志信息误导排查方向。 - 空引用直接调用ToString():
response.Content可能为null(比如请求失败、API无返回内容),直接调用ToString()触发空引用异常。 - Windows服务权限环境差异:服务默认以Local System账户运行,可能无法访问运行在用户会话的localhost API,而计划任务以当前用户身份执行,权限足够。
- 线程终止方式不规范:
Thread.Abort()会强制终止线程,可能导致资源泄漏或不稳定,属于不推荐的做法。 - 日志方法存在缺陷:使用
DateTime.Today只能记录日期,时间固定为00:00;文件流未用using语句自动释放,存在资源泄漏风险。
修复方案与代码优化
1. 修正日志判断逻辑
将实例是否为null的判断条件反转,确保日志记录准确:
var client = new RestClient(); if (client == null) // 反转判断条件 { AgregaLog("RestClient fallo", "ejecutarWS"); return; // 实例创建失败时直接退出方法 } var request = new RestRequest("http://localhost:7113/Home/SincronizarDatos"); request.Method = Method.Post; if (request == null) { AgregaLog("request fallo", "ejecutarWS"); return; } var response = client.Execute(request); if (response == null) { AgregaLog("response fallo", "ejecutarWS"); return; }
2. 处理response.Content空值
在调用ToString()前先检查是否为null:
string resultContent = response.Content ?? "API无返回内容"; AgregaLog("resultado: " + resultContent, "ejecutarWS");
3. 调整Windows服务运行账户
- 打开服务管理器,找到你的服务,右键选择「属性」
- 切换到「登录」选项卡,选择「此账户」,输入有API访问权限的用户账户和密码
- 重启服务,确保服务能访问localhost的API
4. 用CancellationToken优雅终止线程
替换Thread.Abort(),使用取消令牌实现优雅终止:
private CancellationTokenSource _cts; protected override void OnStart(string[] args) { try { AgregaLog("Inicio el servicio", "OnStart"); _cts = new CancellationTokenSource(); Task.Run(() => working(_cts.Token), _cts.Token); } catch (Exception ex) { AgregaLog("Ocurrio un problema:" + ex.Message, "OnStart"); throw; } } protected override void OnStop() { try { _cts?.Cancel(); AgregaLog("Servicio detenido", "OnStop"); } catch (Exception ex) { AgregaLog("Ocurrio un problema:" + ex.Message, "OnStop"); throw; } } public void working(CancellationToken token) { int i = 0; while (!token.IsCancellationRequested) { AgregaLog("Ejecutando linea " + i, "working"); i++; ejecutarWS(); // 等待时响应取消请求 token.WaitHandle.WaitOne(tiempo * 60 * 1000); // 修正Sleep时间(原代码少打一个0) } }
5. 优化日志方法
使用DateTime.Now记录准确时间,用using自动释放资源:
public void AgregaLog(string valor, string metodo) { string logFilePath = "C:\\Logs_Servicio_TankFarm_Users\\"; try { Directory.CreateDirectory(logFilePath); // 无需判断,CreateDirectory已处理存在的情况 logFilePath = Path.Combine(logFilePath, $"Log-{DateTime.Now:dd-MM-yyyy}.txt"); using (var writer = new StreamWriter(logFilePath, true)) { writer.WriteLine($"{DateTime.Now:[dd-MM-yyyy HH:mm:ss]}-METODO: {metodo}-- Desc: {valor}"); } } catch (Exception ex) { // 可考虑将异常写入系统事件日志 System.Diagnostics.EventLog.WriteEntry("MyNewService", $"日志写入失败: {ex.Message}", System.Diagnostics.EventLogEntryType.Error); } }
内容的提问来源于stack exchange,提问作者Leonardo Carrasco Velazquez
相关产品推荐
相关产品推荐

