2012-11-03 28 views
5

Đôi khi người dùng muốn lập lịch một bộ hẹn giờ lớn và không muốn quản lý các tham chiếu đến các bộ hẹn giờ đó.
Trong trường hợp người dùng không tham chiếu bộ hẹn giờ, bộ hẹn giờ có thể được GC thu thập trước khi nó được thực thi.
tôi tạo ra các Timers lớp để phục vụ như một giữ chỗ cho giờ mới được tạo ra:Rò rỉ bộ nhớ xung quanh hẹn giờ ủy nhiệm

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)); 
    } 
} 

Nếu tôi không vứt bỏ bộ đếm thời gian lấy ra, nó kết quả với một rò rỉ bộ nhớ.
Có vẻ như ai đó đang giữ tham chiếu đến đại biểu của người theo dõi sau khi bộ hẹn giờ bị xóa khỏi bộ sưu tập _timers.

Câu hỏi đặt ra là tại sao tôi bị rò rỉ bộ nhớ nếu tôi không bỏ hẹn giờ?

+0

Có lẽ tôi không hiểu những gì bạn đang yêu cầu, bởi vì nó có vẻ như bạn đang tự hỏi tại sao một cái gì đó đối xử tệ bạc khi bạn ngược đãi nó ... –

+0

Tôi hiểu rằng tài liệu nói rằng các thành phần cần được xử lý. Tôi vẫn tò mò những gì ngăn cản từ GC để thu thập các bộ đếm thời gian và các đại biểu nhất định mà không cần gọi phương pháp vứt bỏ. –

Trả lời

9

Timer được giữ lại bởi GCHandle được tạo bởi chính bộ hẹn giờ. Điều này có thể được kiểm tra bằng cách sử dụng một trình thu thập bộ nhớ .net. Lần lượt, các Timer sẽ giữ cho các đại biểu còn sống, mà sau đó sẽ giữ cho phần còn lại còn sống.

A GCHandle là một loại đối tượng đặc biệt có thể được sử dụng để "lừa" Bộ gom rác để giữ cho các đối tượng không thể tiếp cận được.

Bạn có thể thực sự loại-của thử nghiệm này mà không có một hồ sơ bằng:

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

Điều đó thật thú vị. Tôi tự hỏi tại sao tài liệu System.Threading.Timer cho biết: "Miễn là bạn đang sử dụng Bộ hẹn giờ, bạn phải giữ tham chiếu đến nó. Như với bất kỳ đối tượng được quản lý nào, Bộ hẹn giờ sẽ bị thu thập rác khi không có tham chiếu đến nó Thực tế là Bộ hẹn giờ vẫn hoạt động không ngăn nó được thu thập. " Tôi cũng nhận thấy rằng bộ đếm thời gian có thể không được thực thi nếu không có tham chiếu nào được giữ cho bộ hẹn giờ. –

+0

Dường như đây là cách các nhà phát triển dự định nó hoạt động. Nếu tôi chạy chương trình thử nghiệm của tôi trong .net 4.0 hoặc cao hơn có vẻ như các đối tượng được thu thập. –

4

TimersComponents. Như vậy, bạn phải gọi số Dispose khi bạn đã hoàn tất.

Từ the documentation:

Một Component nên giải phóng các nguồn lực một cách rõ ràng bởi các cuộc gọi đến phương pháp Dispose của nó, mà không cần chờ cho quản lý bộ nhớ tự động thông qua một cuộc gọi ngầm với phương pháp Finalize. Khi Container được xử lý, tất cả các thành phần trong số Container cũng được xử lý.

Phần "Khi Container được xử lý, tất cả các thành phần trong Container cũng được xử lý". có thể được nhìn thấy trong phương thức Vứt bỏ của Mẫu khi gọi:

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

Vì vậy, đừng mong đợi định giờ được xử lý với Biểu mẫu trừ khi chúng được thêm vào thành phần.

Cập nhật cho nhận xét của bạn:
Bộ hẹn giờ có con trỏ tới mã không được quản lý (API hẹn giờ của OS) để nó không thể xử lý cho đến khi không còn cần thiết nữa. Trình hoàn thiện sẽ không chạy trên đối tượng trừ khi vứt bỏ được gọi là đầu tiên hoặc chương trình đang thoát. Điều này là do các tham chiếu hiện tại này đối với mã không được quản lý.

Từ những gì tôi hiểu mô hình vứt bỏ được giả sử để tăng tốc các chương trình đóng (vì thời gian chạy có thể thu thập rác trong thời gian xuống) trong khi vẫn cho phép thực thi mã không được quản lý. Nếu bạn làm một số lượng lớn ddl nhập khẩu, bạn sẽ bắt đầu thấy lý do tại sao hệ thống hoạt động theo cách của nó.

Cũng cần lưu ý rằng tài liệu chỉ ra rằng bạn có thể không có quyền truy cập vào các đối tượng được quản lý từ trình kết thúc của đối tượng. Một ví dụ về điều này là một StreamWriter. Cá nhân tôi nghĩ rằng đây là một quy tắc tùy ý, nhưng nó tồn tại, do đó sự cần thiết cho hệ thống vứt bỏ.

Dù bằng cách nào, nếu bạn sử dụng một cái gì đó thực hiện giao diện iDisposable, bạn nên luôn luôn vứt bỏ nó khi bạn đang thực hiện với nó. Bạn sẽ nhận được kết quả tốt hơn (nhất quán hơn) theo cách đó.

+0

Phải. Nhưng bộ đếm thời gian sẽ được GC thu thập và sau đó gọi trình hoàn chỉnh sẽ giải phóng đại biểu và phần còn lại của tài nguyên. Câu hỏi của tôi là tại sao phương thức Vứt bỏ được yêu cầu trong trường hợp đó. –

+0

@OronNadiv Một bộ hẹn giờ có con trỏ tới mã không được quản lý (API hẹn giờ của OS) để nó không thể xử lý cho đến khi không còn cần thiết nữa. trình hoàn thiện sẽ không chạy trên đối tượng trừ khi vứt bỏ được gọi là đầu tiên hoặc chương trình đang thoát vì các tham chiếu hiện tại này tới mã không được quản lý. – Trisped

+1

Có vẻ như anh ta đang sử dụng 'System.Threading.Timer', không phải là' Component'. –