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

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构造函数

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。主要可能原因:

  1. async void 导致上下文失控:SelectTask使用async void签名,异步操作脱离宿主生命周期管理,诊断组件(如App Insights SqlClient监听)的上下文被提前释放,导致telemetry未被正确初始化。
  2. DI作用域提前释放:BeatAsync中scope的using块在RunTasksAsync完成后立即释放,但SelectTask是递归调用,后续异步操作访问已释放的服务,引发诊断组件异常。
  3. 控制台环境下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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 00:27:10