2012-11-03 3 views
5

사용자가 많은 타이머 세트를 예약하려고하고 해당 타이머에 대한 참조를 관리하지 않으려는 경우가 있습니다.
사용자가 타이머를 참조하지 않는 경우 타이머가 실행되기 전에 GC에 의해 타이머가 수집 될 수 있습니다.
나는 새로 만든 타이머에 대한 자리 표시 자 역할을하는 클래스 타이머를 생성 : 나는 제거 타이머를 폐기하지 않으면타이머 위임시 메모리 누수

static class Timers 
{ 
    private static readonly ILog _logger = LogManager.GetLogger(typeof(Timers)); 

    private static readonly ConcurrentDictionary<Object, Timer> _timers = new ConcurrentDictionary<Object, Timer>(); 

    /// <summary> 
    /// Use this class in case you want someone to hold a reference to the timer. 
    /// Timer without someone referencing it will be collected by the GC even before execution. 
    /// </summary> 
    /// <param name="dueTime"></param> 
    /// <param name="action"></param> 
    internal static void ScheduleOnce(TimeSpan dueTime, Action action) 
    { 
     if (dueTime <= TimeSpan.Zero) 
     { 
      throw new ArgumentOutOfRangeException("dueTime", dueTime, "DueTime can only be greater than zero."); 
     } 
     Object obj = new Object(); 

     Timer timer = new Timer(state => 
     { 
      try 
      { 
       action(); 
      } 
      catch (Exception ex) 
      { 
       _logger.ErrorFormat("Exception while executing timer. ex: {0}", ex); 
      } 
      finally 
      { 
       Timer removedTimer; 
       if (!_timers.TryRemove(obj, out removedTimer)) 
       { 
        _logger.Error("Failed to remove timer from timers"); 
       } 
       else 
       { 
        removedTimer.Dispose(); 
       } 
      } 
     }); 
     if (!_timers.TryAdd(obj, timer)) 
     { 
      _logger.Error("Failed to add timer to timers"); 
     } 
     timer.Change(dueTime, TimeSpan.FromMilliseconds(-1)); 
    } 
} 

, 그것은 메모리 누수로 발생합니다.
타이머가 _timers 컬렉션에서 제거 된 후 누군가가 타이머 대리자에 대한 참조를 보유하고있는 것처럼 보입니다.

질문 : 왜 타이머를 처리하지 않으면 메모리 누수가 발생합니까?

+0

어쩌면 내가, 당신이 요구하는지 이해하지 않는다 ... –

+0

설명서에 구성 요소가 폐기되어야한다고 나와 있음을 이해합니다. 여전히 GC에서 dispose 메서드를 호출하지 않고 타이머 및 주어진 대리자를 수집하지 못하게하는 것이 궁금합니다. –

답변

9

Timer은 타이머 자체에 의해 생성 된 GCHandle에 의해 유지됩니다. 이것은 .net 메모리 프로파일 러를 사용하여 테스트 할 수 있습니다. 차례로 Timer은 대리인을 활성 상태로 유지하여 나머지는 그대로 유지합니다.

GCHandle은 가비지 수집기를 "트릭하여"도달 할 수없는 객체를 유지하는 데 사용할 수있는 특별한 종류의 객체입니다.

당신은 할 수 실제로 종류-의 사용 프로파일 러없이 시험이 : 당신이 궁금해하는 것처럼 소리 때문에 당신이 그것을 학대 할 때 뭔가 무례한 행동 이유를

var a = new ClassA(); 
var timer = new Timer(a.Exec); 

var refA = new WeakReference(a); 
var refTimer = new WeakReference(timer); 

a = null; 
timer = null; 

GC.Collect(); 
GC.WaitForPendingFinalizers(); 
GC.Collect(); 

Console.WriteLine(refA.IsAlive); 
Console.WriteLine(refTimer.IsAlive); 
+0

그건 재미 있어요. System.Threading.Timer 문서에서 "타이머를 사용하는 한 참조를 유지해야합니다. 관리 대상 개체와 마찬가지로 Timer는 참조가 없을 때 가비지 수집 대상이됩니다. Timer가 여전히 활성 상태라는 사실은이 타이머가 수집되는 것을 막지는 못합니다. " 타이머에 대한 참조가 없다면 타이머가 실행되지 않을 수도 있습니다. –

+0

이것이 개발자가 의도 한 것처럼 보입니다. .net 4.0 이상에서 테스트 프로그램을 실행하면 오브젝트가 수집되는 것 같습니다. –

4

TimersComponents입니다. 따라서 끝나면 Dispose으로 전화해야합니다. the documentation에서

다음 Finalize 방법에 대한 암시 적 호출을 통해 자동 메모리 관리를 기다리지 않고, 그 Dispose 방법에 대한 호출에 의해 명시 적으로 자원을 해제한다

구성 요소. Container이 처분 될 경우 Container 내의 모든 구성 요소도 폐기됩니다.

"Container이 처분 될 때, Container 내의 모든 구성 요소도 폐기됩니다."

if (disposing && (components != null)) 
{ 
    components.Dispose(); 
} 

그래서 그들은 구성 요소에 추가 된 않는 한 타이머가 양식에 배치 될 기대하지 않습니다 : 그것은 호출 할 때 양식의 폐기 방법에서 볼 수 있습니다.

업데이트 :
타이머에는 관리되지 않는 코드 (OS의 타이머 API)에 대한 포인터가 있으므로 타이머가 더 이상 필요하지 않을 때까지 처리 할 수 ​​없습니다. dispose가 먼저 호출되거나 프로그램이 종료되지 않으면 finalizer는 객체에서 실행되지 않습니다. 이는 관리되지 않는 코드에 대한 이러한 현재 참조 때문입니다.

처분 모델은 관리되지 않는 코드의 실행을 허용하면서 프로그램 닫기 (실행 시간은 휴지 시간 동안 휴지통을 수집 할 수 있기 때문에)를 빠르게한다고 가정합니다. 많은 수의 ddl 가져 오기를 수행하면 시스템이 왜 그렇게 작동하는지 볼 수 있습니다.

설명서는 개체의 최종 자 처리기에서 관리되는 개체에 액세스 할 수 없음을 나타냅니다. 예를 들어 StreamWriter입니다. 개인적으로 나는 이것이 임의적 인 규칙이라고 생각하지만 그것은 존재합니다. 따라서 처분 시스템에 대한 필요성이 존재합니다.

iDisposable 인터페이스를 구현하는 무언가를 사용하는 경우 언제든지 끝내면 처리해야합니다. 그렇게하면 더 나은 결과를 얻을 수 있습니다.

+0

오른쪽. 그러나 타이머는 GC에서 수집 한 다음 위임자와 나머지 리소스를 해제 할 종료자를 호출합니다. 내 질문은 그 이유는 Dispose 메서드가 필요한 이유입니다. –

+0

@OronNadiv 타이머에는 관리되지 않는 코드 (OS의 타이머 API)에 대한 포인터가 있으므로 더 이상 필요하지 않을 때까지 처리 할 수 ​​없습니다. dispose가 먼저 호출되거나 관리되지 않는 코드에 대한 이러한 현재 참조로 인해 프로그램이 종료되지 않으면 finalizer는 개체에서 실행되지 않습니다. – Trisped

+1

그는 System.Threading.Timer를 사용하고있는 것으로 보입니다. System.Threading.Timer는 Component가 아닙니다. –