2015-01-26 2 views
3

저는 비동기 프로그래밍에 대한 전문가가 아니므로 문제가 있는지 확인하고 싶습니다.이 종속성은 Windsor 싱글 톤 스레드로부터 안전합니까?

캐슬 윈저를 사용하지만 특정 ASP.NET 기능에 내장 된 HttpConfiguration.Services 파이프 라인을 사용하는 웹 API 앱이 있습니다. 이 경우 전역 예외 처리기를 등록합니다. 여기에 코드입니다 : 처리되지 않은 예외가 발생하면

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에서 배선하고 응용 프로그램 시작시 등록한 처리기입니다. 결코 존재하지 인해 부모 싱글에

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는 응용 프로그램 시작시 해결 및 컨테이너의 평생 동안 지속되는 : 여기

내가 정의한 핸들 오버라이드를 호출하는 .NET 프레임 워크 코드의 폐기. 따라서 예외를 던지는 동시 요청으로 인해 각각의 스레드가 MailMessage을 관리하는 코드로 들어가서 바람직하지 않은 상태로 남을 수 있습니다.

복잡성은 동시 스레드가 MailMessage의 상태를 변경할 수있는 잠재적 인 문제가 있는지 파악할 수있을뿐만 아니라 스레드를 올바르게 처리하여 문제를 해결할 수 있는지 확인해야합니다. 교착 상태는 흐름의 비동기 특성 때문입니다.

이 문제는 내가 그것을 해결하기 위해 여러 가지 방법을 생각할 수 있어야합니다 :

  • 는 메시지의 설정 주위에 잠금 문을 작성하고 이메일로 전송. void 메쏘드 자체가 비동기 적이기 때문에, 유일한 단점은 동시 스레드가 진입 할 때까지 차단하는 것 같습니다. 이 또한 SemaphoreSlimWait() 방법을 사용하는 것과 같지 않습니까?

  • Typed 팩토리 종속성을 만들고 Handle 메서드에서 MailMessage의 인스턴스를 명시 적으로 해결하고이를 로컬 변수에 할당합니다.

  • async를 끝까지 사용하고 SemaphoreSlim.WaitAsync()을 호출하여 다른 스레드를 차단하지 마십시오.

이 같이

:

public override async void Handle(ExceptionHandlerContext context) 
{ 
    await _semaphoreSlim.WaitAsync(); 
    try 
    { 
     await _errorSmtpClient.SendMailAsync(_errorMail); 
    } 
    finally 
    { 
     _semaphoreSlim.Release(); 
    } 
} 
+2

예, 일반적으로 일시적인 종속성이있는 단일 구성 요소가있는 것이 우려의 원인 일 수 있습니다. 외부 구성 요소를 일시적으로 만들 수없는 경우 옵션 2로 이동합니다. 또는 AppSettings에서 여전히 채워질 수있는 자체 Singleton ErrorMailSettings 클래스는 필요하며 내부에서 필요에 따라 MailMessage를 동적으로 만듭니다. 귀하의 Handle() 메소드. –

+0

나는 설정이 응용 프로그램의 수명 동안 변경되지 않기 때문에 메시지가 아닌 설정을 주입하는 아이디어를 좋아합니다. 이것이 내 시나리오의 편리한 해결 방법이지만, 국가와의 의존성을 주입해야하는 상황을 처리하는 방법을 묻는 것은 가치가 있다고 생각합니다. – moarboilerplate

+0

나는 정말로 우려의 영역이 2 개 있다는 사실을 설명하기 위해 약간의 질문을 업데이트했다. – moarboilerplate

답변

4

어느 종속성싱글 자체는 스레드 안전합니다.

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를 처리하는 것 같습니다.

+0

이 질문에 대해 현상금을 수여 할 예정이지만,이 클래스를 사용하지 않을 때 코드를 스레드로부터 안전하게 할 수있는 방법에 대한 의문점이 여전히 남아 있다고 생각합니다. – moarboilerplate

+0

일단 내가이 클래스를 사용하지 않고 떠난 문제는 내부에서 컨테이너를 참조하지 않고 싱글 톤 클래스에서 일시적인 종속성을 처리하는 방법이었습니다. 필자는 TypedFactoryFacility를 사용하여 객체에 대한 팩토리를 만들었습니다. – moarboilerplate

+0

TypedFactoryFacility는 Castle.Windsor를 사용할 때 이것을 수행하는 한 가지 방법입니다. 내가 "역동적 인 건축"을 언급했을 때 이것은 내가 염두에 두었던 것입니다. 팩토리 인터페이스는 컨테이너를 참조 할 필요가 없으며 공장의 Release 메소드가 올바른 처리를 담당합니다. –