TPL Task 객체에서 Dispose ()를 호출하지 않는 것이 허용됩니까?


123

백그라운드 스레드에서 실행되도록 작업을 트리거하고 싶습니다. 작업 완료를 기다리고 싶지 않습니다.

.net 3.5에서는이 작업을 수행했을 것입니다.

ThreadPool.QueueUserWorkItem(d => { DoSomething(); });

.net 4에서는 TPL이 권장되는 방법입니다. 내가 본 일반적인 패턴은 다음과 같습니다.

Task.Factory.StartNew(() => { DoSomething(); });

그러나, StartNew()방법은 반환 Task되는 구현 객체 IDisposable. 이 패턴을 추천하는 사람들은 이것을 간과하는 것 같습니다. Task.Dispose()방법 에 대한 MSDN 설명서에는 다음과 같은 내용이 있습니다.

"작업에 대한 마지막 참조를 해제하기 전에 항상 Dispose를 호출하십시오."

작업이 완료 될 때까지 작업에 대해 dispose를 호출 할 수 없으므로 주 스레드가 대기하고 dispose를 호출하면 처음에 백그라운드 스레드에서 수행하는 요점이 무너집니다. 또한 정리에 사용할 수있는 완료 / 종료 된 이벤트가없는 것 같습니다.

Task 클래스의 MSDN 페이지는 이에 대해 언급하지 않으며 "Pro C # 2010 ..."책은 동일한 패턴을 권장하며 작업 처리에 대해서는 언급하지 않습니다.

내가 그냥두면 파이널 라이저가 결국 그것을 잡을 것이라는 것을 알고 있지만, 내가 많은 불을 지르고 이와 같은 작업을 잊어 버리고 파이널 라이저 스레드가 압도 당할 때 이것이 돌아와서 나를 물게 될까요?

그래서 내 질문은 다음과 같습니다.

  • 그것은 호출하지 받아 들일 Dispose()Task이 경우 클래스? 그렇다면 왜 그리고 위험 / 결과가 있습니까?
  • 이에 대해 설명하는 문서가 있습니까?
  • 아니면 Task내가 놓친 물건을 처분하는 적절한 방법이 있습니까?
  • 아니면 TPL로 작업을 수행하고 잊어 버리는 다른 방법이 있습니까?

답변:


108

MSDN 포럼에서 이에 대한 토론이 있습니다 .

Microsoft pfx 팀의 구성원 인 Stephen Toub는 다음과 같이 말합니다.

Task.Dispose는 대기중인 스레드가 실제로 차단해야하는 경우 (대기중인 작업을 회전하거나 잠재적으로 실행하는 것과는 반대로) 작업이 완료 될 때까지 대기 할 때 사용되는 이벤트 핸들을 잠재적으로 래핑하기 때문에 존재합니다. 당신이하고있는 모든 것이 연속을 사용하는 것이라면, 그 이벤트 핸들은 절대 할당되지 않을 것입니다
...
일을 처리하기 위해 마무리에 의존하는 것이 더 낫습니다.

업데이트 (2012 년 10 월)
Stephen Toub는 Do I need to dispose of Tasks? 라는 제목의 블로그를 게시했습니다 . 좀 더 자세한 정보를 제공하고 .Net 4.5의 개선 사항을 설명합니다.

요약 : Task99 %의 시간 동안 개체 를 처리 할 필요가 없습니다 .

개체를 폐기하는 두 가지 주된 이유는 관리되지 않는 리소스를 적시에 결정적인 방식으로 확보하고 개체의 종료자를 실행하는 비용을 피하는 것입니다. Task대부분의 경우 다음 중 어느 것도 적용되지 않습니다 .

  1. .Net 4.5부터 a Task가 내부 대기 핸들 ( Task객체 의 유일한 관리되지 않는 리소스)을 할당 하는 유일한 시간 IAsyncResult.AsyncWaitHandleTask, 및
  2. Task객체 자체는 종료자가 없습니다; 핸들 자체는 종료자가있는 객체로 둘러싸여 있으므로 할당되지 않는 한 실행할 종료자가 없습니다.

3
감사합니다, 흥미 롭습니다. 그래도 MSDN 문서에 위배됩니다. MS 또는 .net 팀에서 이것이 허용되는 코드라는 공식적인 말이 있습니까? 그 토론의 끝에서 제기 된 지점이있다 그 "무엇 구현이 향후 버전에서 변경되는 경우"
사이먼 P 스티븐스

실제로 해당 스레드의 응답자가 실제로 Microsoft에서 작동하는 것으로 보이며 pfx 팀에있는 것처럼 보이므로 이것이 일종의 공식적인 대답이라고 생각합니다. 그러나 모든 경우에 작동하지 않는다는 제안이 있습니다. 잠재적 인 누수가있는 경우 안전한 ThreadPool.QueueUserWorkItem으로 되 돌리는 것이 더 낫습니까?
Simon P Stevens

예, 호출하지 않을 Dispose가 있다는 것이 매우 이상합니다. 여기 msdn.microsoft.com/en-us/library/dd537610.aspx 및 여기 msdn.microsoft.com/en-us/library/dd537609.aspx 에서 샘플을 살펴보면 작업을 처리하지 않습니다. 그러나 MSDN의 코드 샘플은 때때로 매우 나쁜 기술을 보여줍니다. 또한 질문에 대답 한 사람은 Microsoft에서 작동합니다.
Kirill Muzykov

2
@Simon : (1) 인용 한 MSDN 문서는 일반적인 조언이며, 특정 사례에는보다 구체적인 조언이 있습니다 (예 : UI 스레드에서 코드를 실행할 EndInvoke때 WinForms 에서 사용할 필요가 없음 BeginInvoke). (2) Stephen Toub는 PFX (예 : channel9.msdn.com ) 의 효과적인 사용에 대한 정규 연사로 잘 알려져 있으므로 누구든지 좋은 지침을 제공 할 수 있다면 그럴 것입니다. 그의 두 번째 단락에 유의하십시오. 결승 자에 일을 맡기는 것이 더 좋습니다.
Richard

12

이것은 Thread 클래스와 같은 종류의 문제입니다. 5 개의 운영 체제 핸들을 사용하지만 IDisposable을 구현하지 않습니다. 원래 디자이너의 좋은 결정이지만 Dispose () 메서드를 호출하는 합리적인 방법은 거의 없습니다. 먼저 Join ()을 호출해야합니다.

Task 클래스는 여기에 하나의 핸들, 즉 내부 수동 재설정 이벤트를 추가합니다. 가장 저렴한 운영 체제 리소스입니다. 물론 Dispose () 메서드는 Thread가 사용하는 5 개의 핸들이 아닌 하나의 이벤트 핸들 만 해제 할 수 있습니다. 그래, 귀찮게 하지마 .

작업의 IsFaulted 속성에 관심이 있어야합니다. 매우 추악한 주제 이며이 MSDN Library 기사 에서 이에 대한 자세한 내용을 읽을 수 있습니다 . 이 문제를 제대로 처리하고 나면 코드에서 작업을 처리 할 수있는 좋은 위치가 있어야합니다.


6
그러나 작업은 Thread대부분의 경우 생성하지 않고 ThreadPool을 사용합니다.
svick

-1

누군가가이 게시물에 표시된 기술에 무게를두고보고 싶습니다. C #에서 Typesafe fire-and-forget 비동기 델리게이트 호출

간단한 확장 메서드가 작업과 상호 작용하는 모든 사소한 경우를 처리하고 dispose를 호출 할 수있는 것처럼 보입니다.

public static void FireAndForget<T>(this Action<T> act,T arg1)
{
    var tsk = Task.Factory.StartNew( ()=> act(arg1),
                                     TaskCreationOptions.LongRunning);
    tsk.ContinueWith(cnt => cnt.Dispose());
}

3
물론에서 Task반환 한 인스턴스 를 처리하는 데 실패 ContinueWith하지만 Stephen Toub의 인용문이 허용되는 대답입니다. 작업에 대해 차단 대기를 수행하지 않는 경우 처리 할 것이 없습니다.
리처드

1
Richard가 언급했듯이 ContinueWith (...)는 두 번째 Task 객체도 반환하며이 객체는 폐기되지 않습니다.
Simon P Stevens

1
따라서 ContinueWith 코드는 이전 작업을 처리하기 위해 다른 작업이 생성되기 때문에 실제로 중복되는 것보다 더 나쁩니다. 이것이 의미하는 방식으로는 기본적으로이 코드 블록에 차단 대기를 도입 할 수 없습니다. 전달한 액션 델리게이트가 태스크 자체를 조작하려고 시도한 경우도 맞습니까?
Chris Marisic 2010

1
두 번째 작업을 처리하기 위해 약간 까다로운 방식으로 람다가 변수를 캡처하는 방법을 사용할 수 있습니다. Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); });
Gideon Engelberth 2011

겉보기에 작동해야 할 @GideonEngelberth. disper는 GC에 의해 폐기되지 않아야하므로 참조가 여전히 유효하다고 가정하고 람다가 폐기를 호출 할 때까지 유효해야합니다. 빈 try / catch가 필요할 수 있습니까?
Chris Marisic 2011
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.