.net Core 6 Web Api 中的调解器 Objectdisposedexception
Mediator in .net Core 6 WebApi Object disposed exception
我有以下 WebApi 构造函数:
public MyController(ILogger<MyController> logger,
Channel channel,
AuditManager auditManager,
IMediator mediator)
{
_logger = logger;
_channel = channel;
_channel.EventFinished += async (o, e) =>
{
await mediator.Publish(new EventFinishedNotification(e);
};
_auditManager = auditManager;
}
基本上我订阅了 _channel.EventFinished
事件(当我的一个端点被调用时触发)。发生这种情况时,在调用 mediator.Publish
时我得到一个
Cannot access a disposed object. Object name: 'IServiceProvider'.'
异常。有什么想法吗?
好吧,为了其他人的利益,问题似乎是从构造函数初始化的回调中调用调解器发布,就像我正在做的那样。我已经将那部分代码包装在一个单独的服务中,并且工作正常。
它发生了,因为您订阅了从 DI 解析的 IMediator
实例。这里编译器在 +=
之后为委托私有化 class 并在其中“缓存”IMediator
的实例。
当您的控制器方法执行结束时,DI 正在处理容器。如果在那之后事件触发,您的方法调用 mediator.Publish
,它在内部使用服务定位器模式并尝试从 IServiceProvider
解析所需的服务。因为它已经被处理掉了,所以你捕获了一个异常。
有几种处理方式:
- 更改其工作方式和体系结构,例如将 classic 事件更改为进程中事件引发。 (当然需要额外的代码)
- 为此目的创建托管服务,它将正确处理 DI 和生命周期,例如:
public sealed class ChannelHostedService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly Channel _channel;
// Resolve channel from DI or initialize it in constructor.
public ChannelHostedService(IServiceProvider serviceProvider, Channel channel)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
// Code for start channel listening.
// Our new subscriber.
_channel.EventFinished += async (o, e) =>
{
// Here we creating new scope from IServiceProvider to handle our event
using (var scope = _serviceProvider.CreateScope())
{
// Mediator resolves from our scope.
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new EventFinishedNotification(e);
}
};
}
}
然后在Startup.cs
中的DI容器中注册它
services.AddHostedService<ChannelHostedService>();
在这个例子中,我们在单例 BackgroundService
中注入单例 IServiceProvider
,并创建新的服务范围以从 IServiceProvider
执行。显然,在这种情况下,您的 Channel
应该是单例。
我没有看到你的代码,也不知道什么是 Channel
以及你为什么在控制器中初始化订阅。但这种方法适用于订阅,例如当我们想从消息代理读取事件时:RabbitMq、Kafka 等。我们只需在 DI 或后台服务构造函数中初始化一次通道,然后它一直工作到我们的应用程序关闭。
希望对你有帮助。
我有以下 WebApi 构造函数:
public MyController(ILogger<MyController> logger,
Channel channel,
AuditManager auditManager,
IMediator mediator)
{
_logger = logger;
_channel = channel;
_channel.EventFinished += async (o, e) =>
{
await mediator.Publish(new EventFinishedNotification(e);
};
_auditManager = auditManager;
}
基本上我订阅了 _channel.EventFinished
事件(当我的一个端点被调用时触发)。发生这种情况时,在调用 mediator.Publish
时我得到一个
Cannot access a disposed object. Object name: 'IServiceProvider'.'
异常。有什么想法吗?
好吧,为了其他人的利益,问题似乎是从构造函数初始化的回调中调用调解器发布,就像我正在做的那样。我已经将那部分代码包装在一个单独的服务中,并且工作正常。
它发生了,因为您订阅了从 DI 解析的 IMediator
实例。这里编译器在 +=
之后为委托私有化 class 并在其中“缓存”IMediator
的实例。
当您的控制器方法执行结束时,DI 正在处理容器。如果在那之后事件触发,您的方法调用 mediator.Publish
,它在内部使用服务定位器模式并尝试从 IServiceProvider
解析所需的服务。因为它已经被处理掉了,所以你捕获了一个异常。
有几种处理方式:
- 更改其工作方式和体系结构,例如将 classic 事件更改为进程中事件引发。 (当然需要额外的代码)
- 为此目的创建托管服务,它将正确处理 DI 和生命周期,例如:
public sealed class ChannelHostedService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly Channel _channel;
// Resolve channel from DI or initialize it in constructor.
public ChannelHostedService(IServiceProvider serviceProvider, Channel channel)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
// Code for start channel listening.
// Our new subscriber.
_channel.EventFinished += async (o, e) =>
{
// Here we creating new scope from IServiceProvider to handle our event
using (var scope = _serviceProvider.CreateScope())
{
// Mediator resolves from our scope.
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new EventFinishedNotification(e);
}
};
}
}
然后在Startup.cs
中的DI容器中注册它services.AddHostedService<ChannelHostedService>();
在这个例子中,我们在单例 BackgroundService
中注入单例 IServiceProvider
,并创建新的服务范围以从 IServiceProvider
执行。显然,在这种情况下,您的 Channel
应该是单例。
我没有看到你的代码,也不知道什么是 Channel
以及你为什么在控制器中初始化订阅。但这种方法适用于订阅,例如当我们想从消息代理读取事件时:RabbitMq、Kafka 等。我们只需在 DI 或后台服务构造函数中初始化一次通道,然后它一直工作到我们的应用程序关闭。
希望对你有帮助。