Console App执行异步任务时SqlClientDiagnosticSourceListener触发空引用异常
控制台应用异步任务执行时触发NullReferenceException错误
问题背景
控制台应用通过依赖注入(DI)启动继承类,传入参数和待执行异步函数。Task类基于ASP.NET Core,主要操作数据库并包含API调用,具备异步特性。当执行Task中含await的异步操作时,会触发NullReferenceException,错误位于SqlClientDiagnosticSourceListener.cs的telemetry.Success = false;行,文件路径为C:\\Users\\%USERNAME%\\AppData\\Local\\Temp\\.vsdbgsrc\\[长十六进制数]\\SqlClientDiagnosticSourceListener.cs。
相关代码如下:
启动代码
using M.BusinessLogic.Heartbeat; using M.BusinessLogic.Heartbeat.Tasks; using M.Data.DataContexts; using M.Data.Models; using M.Heartbeat; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; var heart = new Heart(args, async (scope) => { using var DB = scope.ServiceProvider.GetService<MDbContext>()!; var dbTasks = await (from Tasks in DB.Tasks select Tasks).ToListAsync(); SelectTask(dbTasks, scope, DB); }); await heart.BeatAsync();
Heart构造函数

BeatAsync方法实现
public async Task BeatAsync() { var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder(args); ConfigLoader.LoadConfig(builder.Environment, builder.Services, builder.Configuration, AppInsightsMode.ConsoleApp); var config = builder.Configuration.Get<Config>(); if (config == null) throw new Exception("App Config not loaded!"); builder.Services.AddScoped<IMyAPIGroup, MyAPIGroup>(sp => { var h = config.MyAPI; return new MyAPI.MyApiGroup(...); }); builder.Services.AddSingleton<HeartBeat.Tasks>(); builder.Services.AddScoped<HeartBeat.Utilities>(); //tasks builder.Services.AddScoped<HeartBeat.Task1>(); builder.Services.AddScoped<HeartBeat.Task2>(); builder.Services.AddScoped<HeartBeat.Task3>(); // run the application using (IHost host = builder.Build()) { using (var scope = host.Services.CreateScope()) { await RunTasksAsync(scope); } } }
SelectTask方法
async void SelectTask(List<HeartbeatTask> dbTasks, IServiceScope scope, DbContext DB) { Console.WriteLine("\nHeartbeat Tasks:\n"); for (int i = 0; i < dbTasks.Count; i++) { Console.WriteLine($"{i + 1}. {dbTasks[i].Code}"); } Console.Write("\nEnter the number of the task to run. Will run now, even if disabled:\n\n> "); var input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) { Console.Write("No input selected.\n"); } else { bool isParsable = int.TryParse(input, out int result); if (!isParsable) { Console.WriteLine("Not a number.\n"); } else { int selectedTaskNumber = int.Parse(input); bool isValid = selectedTaskNumber >= 1 && selectedTaskNumber <= dbTasks.Count; if (!isValid) { Console.WriteLine("Invalid task number selected.\n"); } else { IHeartbeatTask? Itask = Tasks.GetTaskFromCode(dbTasks[selectedTaskNumber - 1].Code!, scope); Console.WriteLine($"\nStarted '{Itask.TaskName}' task..."); await Itask!.RunAsync(heartbeatTaskLog.Id); Console.WriteLine($"...finished '{Itask.TaskName}' task."); // save to db after each task is run DB.SaveChanges(); } } } SelectTask(dbTasks, scope, DB); }
问题分析
错误根源在于telemetry对象为null,触发NullReferenceException。主要可能原因:
- async void 导致上下文失控:
SelectTask使用async void签名,异步操作脱离宿主生命周期管理,诊断组件(如App Insights SqlClient监听)的上下文被提前释放,导致telemetry未被正确初始化。 - DI作用域提前释放:
BeatAsync中scope的using块在RunTasksAsync完成后立即释放,但SelectTask是递归调用,后续异步操作访问已释放的服务,引发诊断组件异常。 - 控制台环境下App Insights诊断配置缺失:
AppInsightsMode.ConsoleApp模式下,SqlClient诊断监听未正确初始化,导致无法创建telemetry实例。
解决方案
1. 替换async void为async Task
async void是异步操作的不安全模式,改为async Task以确保任务能被追踪和等待:
async Task SelectTask(List<HeartbeatTask> dbTasks, IServiceScope scope, DbContext DB)
调用处改为:
await SelectTask(dbTasks, scope, DB);
递归调用处也需改为:
await SelectTask(dbTasks, scope, DB);
2. 延长DI作用域与宿主生命周期
调整BeatAsync中的宿主代码,确保作用域和宿主在用户退出前不被释放:
using (IHost host = builder.Build()) { await host.StartAsync(); // 确保所有服务初始化完成 using (var scope = host.Services.CreateScope()) { await RunTasksAsync(scope); } // 等待用户输入后再停止宿主,避免提前释放资源 Console.WriteLine("Press any key to exit..."); Console.ReadKey(); await host.StopAsync(); }
3. 验证或调整SqlClient诊断配置
- 临时禁用诊断监听:添加环境变量验证问题是否由诊断组件引发:
Environment.SetEnvironmentVariable("DOTNET_DISABLE_SQLCLIENT_DIAGNOSTICS", "1"); - 手动注册诊断监听器:如果需要保留诊断功能,在
BeatAsync中手动添加监听器配置:builder.Services.AddSingleton<DiagnosticListener>(sp => { var listener = new DiagnosticListener("Microsoft.Data.SqlClient"); return listener; });
4. 确认DbContext生命周期
确保MDbContext以Scoped模式注册(EF Core默认配置),避免异步操作中使用已释放的DbContext。
内容的提问来源于stack exchange,提问作者Rabbi Kaii
相关产品推荐
相关产品推荐

