Windsor 单例线程安全的这种依赖性吗?
Is this dependency of a Windsor singleton thread-safe?
无论如何我都不是异步编程方面的专家,所以我想验证我是否遇到了问题。
我有一个 Web API 应用程序,它使用 Castle Windsor,但也使用内置 HttpConfiguration.Services
管道来实现某些 ASP.NET 功能。在这种情况下,我正在注册一个全局异常处理程序。这是代码:
protected void Application_Start()
{
//ASP.NET registers this instance in a ConcurrentDictionary and treats it as a singleton
config.Services.Replace(typeof(IExceptionHandler), container.Resolve<IExceptionHandler>());
}
public class EmailExceptionHandler : ExceptionHandler
{
private readonly SmtpClient client;
private MailMessage _errorMail;
public EmailSender(SmtpClient client, MailMessage message)
//client automatically resolved with smtp settings pulled from web.config by container. Seems okay to be a singleton here.
//message automatically resolved with properties like To, From populated from web.config.
//The intent here is to keep config access out of this class for testability.
{
_errorSmtpClient = errorSmtpClient;
_errorMail = errorMail;
}
public override void Handle(ExceptionHandlerContext context)
{
// set props on the MailMessage e.g. exception detail
_errorSmtpClient.SendAsync(_errorMail);
// standard post-processing, no dependencies necessary
}
}
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<SmtpClient>().DependsOn(Dependency.OnAppSettingsValue(/*...*/)));
container.Register(Component.For<MailMessage>().Named("errorMailMessage")
.DependsOn(Dependency.OnAppSettingsValue(/*...*/)).LifestyleTransient());
//transient here should bind lifetime to exceptionhandler singleton's lifetime
container.Register(Component.For<IExceptionHandler>().ImplementedBy<EmailExceptionHandler>()
.DependsOn(Dependency.OnComponent("message", "errorMailMessage")));
}
当发生未处理的异常时,ASP.NET 将在其服务字典中查找已注册的 IExceptionHandler
并将错误上下文传递给它。在本例中,这是我在 Windsor 中连接并在应用程序启动时注册的处理程序。
这是调用我定义的句柄覆盖的 .NET Framework 代码:
Task IExceptionHandler.HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
if (context == null)
throw new ArgumentNullException("context");
ExceptionContext exceptionContext = context.ExceptionContext;
if (!this.ShouldHandle(context))
return TaskHelpers.Completed();
return this.HandleAsync(context, cancellationToken);
}
public virtual Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
this.Handle(context);
return TaskHelpers.Completed();
}
MailMessage
在应用程序启动时被解析并在容器的整个生命周期中持续存在,因为父单例从未被释放。因此,我担心抛出异常的并发请求会导致它们各自的线程进入管理 MailMessage
的代码,可能会使其处于不良状态。
这里的复杂性在于,我不仅要弄清楚是否存在并发线程可以更改 MailMessage
状态的潜在问题,我还必须确保通过管理来解决上述问题由于流的异步性质,线程正确而不会导致死锁。
如果存在问题,我可以想出几种方法来解决它:
围绕消息的设置和电子邮件的发送创建一个锁定语句。由于 void 方法本身不是异步的,唯一的缺点似乎是导致并发线程阻塞,直到它们可以进入。这不也和使用 SemaphoreSlim
的 Wait()
方法一样吗?
创建类型化工厂依赖项并显式解析 Handle 方法中的 MailMessage 实例,并将其分配给局部变量。
不要一直使用异步并调用 SemaphoreSlim.WaitAsync()
来阻塞其他线程——这行得通吗?
像这样:
public override async void Handle(ExceptionHandlerContext context)
{
await _semaphoreSlim.WaitAsync();
try
{
await _errorSmtpClient.SendMailAsync(_errorMail);
}
finally
{
_semaphoreSlim.Release();
}
}
依赖项和单例本身都不是线程安全的。
Instance methods of SmtpClient are not thread-safe. This is confirmed in this question。因此,依赖于单个 SmtpClient 的单例不是线程安全的。
Instance methods of MailMessage are also not thread-safe。所以你的单身人士再次不是线程安全的。
此外,我找不到任何迹象表明 MailMessage 旨在可重用。该对象实现 IDisposable 并封装其他也实现 IDisposable 的对象。消息可能包含非托管资源,因此推断 MailMessage 旨在一次性使用并且在发送后应被处置似乎是合乎逻辑的。有关进一步讨论,请参阅 this question。
如果您希望仅继续使用单个 MailMessage(可重复使用)和单个 SmtpClient,则需要同步这些对象的所有使用。即便如此,我预计您可能仍会遇到未正确释放非托管资源的问题。
似乎最简单和最安全的 ExceptionHandler 实现是在每次调用时动态构造 MailMessage 和 SmtpClient,然后在传输后处理 MailMessage。
无论如何我都不是异步编程方面的专家,所以我想验证我是否遇到了问题。
我有一个 Web API 应用程序,它使用 Castle Windsor,但也使用内置 HttpConfiguration.Services
管道来实现某些 ASP.NET 功能。在这种情况下,我正在注册一个全局异常处理程序。这是代码:
protected void Application_Start()
{
//ASP.NET registers this instance in a ConcurrentDictionary and treats it as a singleton
config.Services.Replace(typeof(IExceptionHandler), container.Resolve<IExceptionHandler>());
}
public class EmailExceptionHandler : ExceptionHandler
{
private readonly SmtpClient client;
private MailMessage _errorMail;
public EmailSender(SmtpClient client, MailMessage message)
//client automatically resolved with smtp settings pulled from web.config by container. Seems okay to be a singleton here.
//message automatically resolved with properties like To, From populated from web.config.
//The intent here is to keep config access out of this class for testability.
{
_errorSmtpClient = errorSmtpClient;
_errorMail = errorMail;
}
public override void Handle(ExceptionHandlerContext context)
{
// set props on the MailMessage e.g. exception detail
_errorSmtpClient.SendAsync(_errorMail);
// standard post-processing, no dependencies necessary
}
}
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<SmtpClient>().DependsOn(Dependency.OnAppSettingsValue(/*...*/)));
container.Register(Component.For<MailMessage>().Named("errorMailMessage")
.DependsOn(Dependency.OnAppSettingsValue(/*...*/)).LifestyleTransient());
//transient here should bind lifetime to exceptionhandler singleton's lifetime
container.Register(Component.For<IExceptionHandler>().ImplementedBy<EmailExceptionHandler>()
.DependsOn(Dependency.OnComponent("message", "errorMailMessage")));
}
当发生未处理的异常时,ASP.NET 将在其服务字典中查找已注册的 IExceptionHandler
并将错误上下文传递给它。在本例中,这是我在 Windsor 中连接并在应用程序启动时注册的处理程序。
这是调用我定义的句柄覆盖的 .NET Framework 代码:
Task IExceptionHandler.HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
if (context == null)
throw new ArgumentNullException("context");
ExceptionContext exceptionContext = context.ExceptionContext;
if (!this.ShouldHandle(context))
return TaskHelpers.Completed();
return this.HandleAsync(context, cancellationToken);
}
public virtual Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
this.Handle(context);
return TaskHelpers.Completed();
}
MailMessage
在应用程序启动时被解析并在容器的整个生命周期中持续存在,因为父单例从未被释放。因此,我担心抛出异常的并发请求会导致它们各自的线程进入管理 MailMessage
的代码,可能会使其处于不良状态。
这里的复杂性在于,我不仅要弄清楚是否存在并发线程可以更改 MailMessage
状态的潜在问题,我还必须确保通过管理来解决上述问题由于流的异步性质,线程正确而不会导致死锁。
如果存在问题,我可以想出几种方法来解决它:
围绕消息的设置和电子邮件的发送创建一个锁定语句。由于 void 方法本身不是异步的,唯一的缺点似乎是导致并发线程阻塞,直到它们可以进入。这不也和使用
SemaphoreSlim
的Wait()
方法一样吗?创建类型化工厂依赖项并显式解析 Handle 方法中的 MailMessage 实例,并将其分配给局部变量。
不要一直使用异步并调用
SemaphoreSlim.WaitAsync()
来阻塞其他线程——这行得通吗?
像这样:
public override async void Handle(ExceptionHandlerContext context)
{
await _semaphoreSlim.WaitAsync();
try
{
await _errorSmtpClient.SendMailAsync(_errorMail);
}
finally
{
_semaphoreSlim.Release();
}
}
依赖项和单例本身都不是线程安全的。
Instance methods of SmtpClient are not thread-safe. This is confirmed in this question。因此,依赖于单个 SmtpClient 的单例不是线程安全的。
Instance methods of MailMessage are also not thread-safe。所以你的单身人士再次不是线程安全的。
此外,我找不到任何迹象表明 MailMessage 旨在可重用。该对象实现 IDisposable 并封装其他也实现 IDisposable 的对象。消息可能包含非托管资源,因此推断 MailMessage 旨在一次性使用并且在发送后应被处置似乎是合乎逻辑的。有关进一步讨论,请参阅 this question。
如果您希望仅继续使用单个 MailMessage(可重复使用)和单个 SmtpClient,则需要同步这些对象的所有使用。即便如此,我预计您可能仍会遇到未正确释放非托管资源的问题。
似乎最简单和最安全的 ExceptionHandler 实现是在每次调用时动态构造 MailMessage 和 SmtpClient,然后在传输后处理 MailMessage。