2009-11-18 5 views
31

나는 일반적으로 양식과 같은 코드가 있습니다C# : 런타임에 생성 된 BackgroundWorker를 처리해야합니까?

private void PerformLongRunningOperation() 
    { 
     BackgroundWorker worker = new BackgroundWorker(); 

     worker.DoWork += delegate 
     { 
      // perform long running operation here 
     }; 

     worker.RunWorkerAsync(); 
    } 

이 내가 다음 폼 디자이너를 추가했다면 나는 그것이 배치 얻을 것이라고 생각하는 반면 나는의 BackgroundWorker을 폐기하지 않는 것을 의미한다.

이 문제가 발생합니까? 모듈 수준을 _saveWorker으로 선언 한 다음 양식의 처리 방법에서 Dispose을 호출하는 것이 더 정확합니까?

답변

32

예, 백그라운드 작업자를 처분해야합니다.

나중에 정리가 필요없는 ThreadPool.QueueUserWorkItem(...)을 쉽게 사용할 수 있습니다.


는 는 항상 폐기()를 호출해야하는 이유에 대한 추가 세부

: 당신은 BackgroundWorker 구성 클래스를 보면 실제로 그 처분 방법의 임의의 스레드가 정리하지 않지만

은 여전히 ​​중요하다

클래스가 가비지 컬렉터에 미치는 영향 때문에 Dispose를 호출해야합니다.

finalizer가있는 클래스는 즉시 GCed되지 않습니다. 그것들은 보관되어 파이널 라이저 큐에 추가됩니다. 파이널 라이저 스레드가 실행되면 (표준 패턴이 dispose를 호출 한 다음) 실행됩니다. 이것은 개체가 GC 생성 1에서 살아남을 수 있음을 의미합니다. 그리고 gen 1 모음은 gen 0 모음보다 훨씬 희귀하므로 개체가 훨씬 오래 동안 메모리에 고정되어 있습니다.

그러나 Dispose()를 호출하면 개체가 최종화 대기열에 추가되지 않으므로 가비지 수집이 자유 롭습니다.

큰 문제는 아니지만 많이 만들면 필요한 것보다 더 많은 메모리를 사용하게됩니다. 항상 dispose 메서드가있는 객체에서 항상 dispose를 호출하는 것이 좋습니다.

그래서 나는 100 % 어렵고 빠른 요구 사항이 아니라고 생각합니다. Dispose()를 호출하지 않으면 앱이 폭발하지 않으며 메모리가 누출되지 않지만 경우에 따라 부정적인 영향을 줄 수 있습니다. 배경 작업자는 WinForms 구성 요소에서 사용하도록 설계되었으므로 다른 요구 사항이 있고 WinForms 구성 요소로 사용하지 않으려는 경우 사용하지 말고 올바른 작업 도구를 사용하십시오. ThreadPool과 같습니다.

+8

_why_는 bgw.Dispose()를 호출해야합니까? 게다가 거기에는 보통 논리가 있기 때문에 논리가 있습니다. –

+0

@Henk. 추가 세부 정보를 추가했습니다. 100 % 중요하지는 않지만, 호출되도록 설계되었습니다. 그것을 호출하고 싶지 않으면 다른 Winforms 코드에 더 적합한 다른 클래스가 있습니다. –

6

Bgw가 보유 할 수있는 유일한 리소스는 스레드입니다. 대리인에 무한 루프가 없으면 괜찮습니다.

BackgroundWorker는 Component에서 IDisposable()을 상속하지만 실제로 필요하지는 않습니다.

메서드를 ThreadPool에서 직접 푸시하는 것과 비교하십시오. 쓰레드를 풀지는 못합니다 (물론 풀에서 쓰레드는 제외).

그러나 Completed 이벤트 또는 진행/취소 기능을 사용하지 않는다는 의미에서 샘플이 완료되면 ThreadPool.QueueUserWorkItem()을 사용할 수도 있습니다.

+2

스레드 객체는 매우 비쌉니다 (적어도 예약 된 스택 메모리로 구성되어 있습니다. 어쩌면 1MB 정도입니다). – denisenkom

+0

예, 가격이 비쌉니다. 그러나 어떻게 그들을 청소할 것입니까? 게다가, Bgw는 ThreadPool을 사용합니다. –

+1

BGW에서 dispose를 호출하는 데는 최소한 2 가지 이유가 있습니다. 첫째로 GC.SuppressFinalize()를 호출합니다. 둘째, Wayne이 향후 구현에 대해 만들고 내부 구현 세부 정보에 의존하지 않기 때문입니다. –

16

이 끝나면 BackgroundWorker만 처분해야합니다. 해당 이벤트는 BackgroundWorker 자체에서 발생하기 때문에 Completed 이벤트에서이를 수행 할 수 없습니다.

BackgroundWorker는 실제로 WinForms 양식의 구성 요소로 사용하기위한 것이므로이를 수행하거나 Thread.QueueUserWorkItem과 같은 것으로 전환하는 것이 좋습니다. 스레드 풀 스레드를 사용하며 완료되면 특수 정리가 필요하지 않습니다.

+4

+1 'BackgroundWorker'를 WinForms 구성 요소로 사용하려는 경우. – Brian

2

모든 IDisposable 개체에 대해 Dispose()를 호출하는 것이 가장 좋습니다. 이를 통해 핸들과 같이 보유 할 수있는 관리되지 않는 리소스를 릴리스 할 수 있습니다. 또한 IDisposable 클래스에는 GC가 해당 객체를 완전히 수집 할 수있는 시간을 지연시킬 수있는 finalizer가 있어야합니다.

클래스가 IDisposable을 할당하고이를 멤버 변수에 할당하면 일반적으로 IDisposable이어야합니다.

그러나 Dispose()를 호출하지 않으면 Finalizer가 결국 호출되어 결국 리소스가 정리됩니다. 이를 명시 적으로 호출하면 애플리케이션 성능을 향상시키고 메모리 사용량을 줄이는 오버 헤드를 줄이고 더 신속하게 이러한 작업을 수행 할 수 있다는 장점이 있습니다.

+1

"IDisposable 클래스에는 또한 GC가 해당 개체를 완전히 수집 할 수있는 시간을 지연시킬 수있는 finalizer가 있어야합니다." <- IDisposable에 정리할 * un * 관리 리소스가있는 경우에만 해당 – dss539

+0

관리되지 않는 리소스를 처리하는 경우 일반적으로 IDisposable 만 구현해야합니다. 그렇지 않으면 GC는 모든 작업을 수행합니다 (아마도 "using"구문의 의미를 활용할 수있는 경우는 제외). 또한 IDisposable 개체는 종종 다른 ID 개체를 참조하기 때문에 관리되지 않는 리소스를 직접 보유하지 않고 간접적으로 유지합니다. – RickNZ

7

일반적으로 IDisposable 인 경우 일반적으로 Dispose()를 수행해야합니다. BackgroundWorker의 현재 구현을 기술적으로 처리 할 필요가 없더라도 나중에있을 수있는 내부 구현에 놀랄 필요가 없습니다.

+1

나는 그것이 미래 구현에 관한 아주 좋은 지적이라고 생각한다. 그렇습니다. 현재 BGW는 처리 방법에는 아무 것도하지 않지만, .net 4.0을 보장 할 방법이 없습니다. BGW는 거기에서 정리를하지 않을 것입니다. –

+1

그게 좋겠는데 진실은 Bgw.Dispose가 기본 클래스의 유산이라는 것입니다. –

+1

@henk ** 당장 **입니다. MS는 언젠가는 그렇지 않을 것이라는 보장을하지 않습니다. ** 정확하게 ** 왜 그들은'IDisposable'을 구현하는 것에'Dispose()'를 항상 권유합니다. –

3

왜 사용 문을 싸지 않으십니까? 별로 별도의 노력과 당신은 처분을받을 :

private void PerformLongRunningOperation() 
    { 
     using (BackgroundWorker worker = new BackgroundWorker()) 
     { 
      worker.DoWork += delegate 
          { 
           // perform long running operation here 
          }; 
      worker.RunWorkerAsync(); 
     } 
    } 

편집 :

using System; 
using System.ComponentModel; 
using System.Threading; 

namespace BackgroundWorkerTest 
{ 
    internal class Program 
    { 
     private static BackgroundWorker _privateWorker; 

     private static void Main() 
     { 
      PrintThread("Main"); 
      _privateWorker = new BackgroundWorker(); 
      _privateWorker.DoWork += WorkerDoWork; 
      _privateWorker.RunWorkerCompleted += WorkerRunWorkerCompleted; 
      _privateWorker.Disposed += WorkerDisposed; 
      _privateWorker.RunWorkerAsync(); 
      _privateWorker.Dispose(); 
      _privateWorker = null; 

      using (var BW = new BackgroundWorker()) 
      { 
       BW.DoWork += delegate 
           { 
            Thread.Sleep(2000); 
            PrintThread("Using Worker Working"); 
           }; 
       BW.Disposed += delegate { PrintThread("Using Worker Disposed"); }; 
       BW.RunWorkerCompleted += delegate { PrintThread("Using Worker Completed"); }; 
       BW.RunWorkerAsync(); 
      } 

      Console.ReadLine(); 
     } 

     private static void WorkerDisposed(object sender, EventArgs e) 
     { 
      PrintThread("Private Worker Disposed"); 
     } 

     private static void WorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      PrintThread("Private Worker Completed"); 
     } 

     private static void WorkerDoWork(object sender, DoWorkEventArgs e) 
     { 
      Thread.Sleep(2000); 
      PrintThread("Private Worker Working"); 
     } 

     private static void PrintThread(string caller) 
     { 
      Console.WriteLine("{0} Thread: {1}", caller, Thread.CurrentThread.ManagedThreadId); 
     } 
    } 
} 

: 내가 좀 처리하고 이것 저것에 무슨 일이 일어나고 있는지보고 함께 약간의 테스트를 넣어

확인 출력은 다음과 같습니다.

Main Thread: 1 
Private Worker Disposed Thread: 1 
Using Worker Disposed Thread: 1 
Private Worker Working Thread: 3 
Using Worker Working Thread: 4 
Using Worker Completed Thread: 4 
Private Worker Completed Thread: 3 

일부 테스트에서 Dispose()는 기본적으로 영향을 미치지 않습니다. 개시 한 BackgroundWorker. using 문 범위 내에서 호출하는지 또는 코드에서 선언 된 코드를 사용하여 즉시 처리하고 참조를 취소하더라도 여전히 정상적으로 실행됩니다. Disposed Event는 주 스레드에서 발생하고 DoWork 및 RunWorkerCompleted는 스레드 풀 스레드에서 발생합니다 (이벤트 발생시 사용할 수있는 스레드 중 하나). Dispose (DoWork가 완료되기 전에)를 호출하고 RunWorkerCompleted가 실행되지 않은 직후에 RunWorkerCompleted 이벤트의 등록을 취소 한 경우를 시도했습니다. 이것은 내가 처분 함에도 불구하고 여전히 BackgroundWorker 객체를 조작 할 수 있다고 믿게합니다.

다른 사람들이 언급했듯이 현재는 Dispose를 호출하는 것이 꼭 필요한 것은 아닌 것처럼 보입니다. 그러나, 나는 적어도 내 경험과 이러한 테스트를 통해서도 해를 입지는 않습니다.

+3

이 방법이 효과가 있습니까? 작업자를 너무 일찍 처분하지 않습니까? – RickL

+0

나는 이것을 여러 번 사용해 왔지만 분명히 "작동"합니다. 너무 일찍 백그라운드 프로세스를 죽이지는 않습니다. 그러나 이것이 정말로 옳은 일인가 아니면 진정한 처분인지에 대한 명확한 답을 찾지 못했습니다. 나는 그것에 대해 확실히 알고있는 누군가를 기대하고 있었다. –

+0

BackgroundWorker의 Disposed 이벤트에 메시지 상자를 추가하고 비동기 작업이 완료된 후 메시지 상자가 표시되었습니다. 나에게 BackgroundWorker는 작업을 완료 한 후에 자체적으로 처분하는 것처럼 보입니다. 그 평가에 동의합니까? – joek1975

3

RunWorkerCompleted 이벤트로 처리를 요청하십시오.

BackgroundWorker wkr = new BackgroundWorker(); 
wkr.DoWork += (s, e) => { 
    // Do long running task. 
}; 
wkr.RunWorkerCompleted += (s, e) => { 
    try { 
     if (e.Error != null) { 
      // Handle failure. 
     } 
    } finally { 
     // Use wkr outer instead of casting. 
     wkr.Dispose(); 
    } 
}; 
wkr.RunWorkerAsync(); 

여분의 시도/마침내 완료 코드는 예외가 발생하는 경우 Dispose가 호출되는 것을 보장하는 것입니다.

0

완료 핸들러는 원래 스레드 (즉, 스레드 풀의 백그라운드 스레드가 아닌)에서 실행됩니다! 테스트 결과는 실제로 그 전제를 확인합니다.

관련 문제