编程语言
首页 > 编程语言> > c#-温莎单例的这种依赖关系线程安全吗?

c#-温莎单例的这种依赖关系线程安全吗?

作者:互联网

无论如何,我都不是异步编程方面的专家,所以我想验证我是否有问题.

我有一个Web API应用程序,该应用程序使用Castle Windsor,但也对某些ASP.NET函数使用了内置的HttpConfiguration.Services管道.在这种情况下,我正在注册一个全局异常处理程序.这是代码:

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中连接并在应用程序启动时注册的处理程序.

这是调用我定义的Handle覆盖的.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.此错误在question中得到确认.因此,依赖于单个SmtpClient的单例不是线程安全的.

Instance methods of MailMessage are also not thread-safe.同样,您的单例也不是线程安全的.

此外,我找不到任何暗示MailMessage可重用的信息.该对象实现IDisposable,并封装也实现IDisposable的其他对象.消息可能包含不受管的资源,因此可以合理地得出结论,认为MailMessage是一次性使用的,一旦发送便应丢弃.请参阅this question,以进行进一步的讨论.

如果只希望继续使用一个已重用的MailMessage和一个SmtpClient,则需要同步使用这些对象.即使这样,我也希望您可能仍会遇到无法正确释放非托管资源的问题.

看来ExceptionHandler的最简单,最安全的实现是每次调用时都会动态构造MailMessage和SmtpClient,然后在传输后处理MailMessage.

标签:multithreading,async-await,castle-windsor,c
来源: https://codeday.me/bug/20191028/1954633.html