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

.NET 6 WebAPI中MQTT接收消息时访问数据库遇上下文已释放错误

问题分析与解决方案

问题根源

  1. 生命周期不匹配:MQTTCheck方法里的using (var scope = scopedFactory.CreateScope())会在service.check()执行后立即释放作用域,而check是async void方法,会直接返回,导致MQTTManager依赖的IUnitOfWork和DatabaseContext被提前销毁。但MQTT客户端是长连接的,后续收到消息时再访问已释放的上下文就会触发报错。
  2. 异步方法滥用:check方法使用async void,这种写法在非事件处理程序中会导致异步操作脱离控制,无法追踪执行状态,进一步加剧了作用域提前释放的问题。

解决方案

1. 调整MQTTManager的注入生命周期

将MQTTManager改为单例注入,因为MQTT客户端是长生命周期的服务,需要持续监听消息:

// Program.cs中修改注入方式
builder.Services.AddSingleton<MQTTManager>();

2. 改用IServiceScopeFactory获取数据库上下文

长生命周期的单例服务不能直接依赖短生命周期的IUnitOfWork,需通过IServiceScopeFactory在每次处理消息时创建独立作用域,获取新鲜的上下文实例:

// MQTTManager.cs修改后的代码
public class MQTTManager
{
    IMqttClient client;
    readonly IServiceScopeFactory _scopeFactory;

    string serverAddress = "XXXXXX";
    int port = 1883;

    public MQTTManager(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    // 替换async void为async Task,避免异步失控
    public async Task CheckAsync()
    {
        try
        {
            client = new MqttFactory().CreateMqttClient();
            var options = new MqttClientOptionsBuilder()
                .WithClientId(Guid.NewGuid().ToString())
                .WithTcpServer(serverAddress, port)
                .WithCleanSession()
                .WithWillRetain(false)
                .Build();

            client.ApplicationMessageReceivedAsync += Client_ApplicationMessageReceivedAsync;

            await client.ConnectAsync(options);
        }
        catch (Exception ex)
        {
            // 建议添加日志记录,不要吞掉异常
        }
    }

    private async Task Client_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs x)
    {
        string topic = x.ApplicationMessage.Topic;
        string receiveMsg = x.ApplicationMessage.ConvertPayloadToString();

        // 每次处理消息时创建独立作用域
        using (var scope = _scopeFactory.CreateScope())
        {
            var uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
            Home home = uow.HomeRepository.Get(h => h.Name == topic);
            // ... 执行数据库操作
            await uow.SaveChangesAsync(); // 如果有数据修改,记得保存
        }

        await Task.CompletedTask;
    }
}

3. 修正Program中的启动逻辑

确保异步方法执行完成,不再提前释放作用域:

// Program.cs修改后的启动代码
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(builder.Configuration.GetSection("ConnectionStrings").GetSection(environment).Value));
builder.Services.AddTransient<IUnitOfWork, UnitOfWork>();
builder.Services.AddSingleton<MQTTManager>();
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();

// 使用异步方法启动MQTT服务
await MQTTCheckAsync(app);

app.MapControllers();
app.Run();

async Task MQTTCheckAsync(IHost app)
{
    var mqttManager = app.Services.GetRequiredService<MQTTManager>();
    await mqttManager.CheckAsync();
}

额外注意事项

  • 禁止在非事件处理程序中使用async void,必须改用async Task,这样才能捕获异常、控制异步流程。
  • 单例服务依赖短生命周期服务时,必须通过IServiceScopeFactory创建临时作用域,避免上下文复用或提前释放的问题。
  • 处理MQTT消息时,每个消息都使用独立的作用域,确保数据库上下文是全新的实例,用完即释放,符合EF Core的最佳实践。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 13:55:16