2009-11-21 24 views
14

Đã có rất nhiều cuộc thảo luận xung quanh việc này và mọi người đều đồng ý rằng bạn nên luôn luôn gọi Delegate.EndInvoke để ngăn chặn rò rỉ bộ nhớ (thậm chí Jon Skeet đã nói nó!).Không gọi Delegate.EndInvoke có thể gây rò rỉ bộ nhớ ... một huyền thoại?

Tôi luôn làm theo hướng dẫn này mà không cần đặt câu hỏi, nhưng gần đây tôi đã triển khai lớp AsyncResult của riêng mình và thấy rằng tài nguyên duy nhất có thể bị rò rỉ là AsyncWaitHandle.

(Thực tế nó không thực sự bị rò rỉ vì tài nguyên gốc được sử dụng bởi WaitHandle được đóng gói trong một SafeHandle trong đó có một Finalizer, nó sẽ thêm áp lực vào hàng đợi cuối cùng của bộ thu gom rác mặc dù vậy. thực hiện asyncResult sẽ chỉ khởi tạo AsyncWaitHandle theo yêu cầu ...)

cách tốt nhất để biết nếu có một sự rò rỉ chỉ là để thử nó:

Action a = delegate { }; 
while (true) 
    a.BeginInvoke(null, null); 

tôi chạy này trong một thời gian và bộ nhớ duy trì từ 9-20 MB.

Hãy so sánh với khi Delegate.EndInvoke được gọi là:

Action a = delegate { }; 
while (true) 
    a.BeginInvoke(ar => a.EndInvoke(ar), null); 

Với xét nghiệm này, vở kịch nhớ giữa 9-30 MG, lạ eh? (Có thể vì mất nhiều thời gian hơn để thực thi khi có AsyncCallback, do đó sẽ có nhiều đại biểu được xếp hàng đợi hơn trong ThreadPool)

Bạn nghĩ gì ... "Huyền thoại bị bẻ khóa"?

P.S. ThreadPool.QueueUserWorkItem là một trăm hiệu quả hơn so với Delegate.BeginInvoke, tốt hơn để sử dụng nó cho lửa & quên cuộc gọi.

+2

Câu hỏi hay! +1. BTW, bạn xem quá nhiều MythBusters;) – RCIX

+0

Tôi đồng ý, ThreadPool.QueueUserWorkItem là một phương pháp tuyệt vời và không được sử dụng. – Anton

Trả lời

6

Tôi đã chạy một thử nghiệm nhỏ để gọi một đại biểu Hành động và ném một ngoại lệ bên trong nó.Sau đó, tôi đảm bảo rằng tôi không làm ngập hồ bơi luồng bằng cách chỉ duy trì một số lượng quy định các luồng chạy cùng một lúc và điền vào nhóm luồng liên tục khi cuộc gọi xóa kết thúc. Đây là mã:

static void Main(string[] args) 
{ 

    const int width = 2; 
    int currentWidth = 0; 
    int totalCalls = 0; 
    Action acc =() => 
    { 
     try 
     { 
      Interlocked.Increment(ref totalCalls); 
      Interlocked.Increment(ref currentWidth); 
      throw new InvalidCastException("test Exception"); 
     } 
     finally 
     { 
      Interlocked.Decrement(ref currentWidth); 
     } 
    }; 

    while (true) 
    { 
     if (currentWidth < width) 
     { 
      for(int i=0;i<width;i++) 
       acc.BeginInvoke(null, null); 
     } 

     if (totalCalls % 1000 == 0) 
      Console.WriteLine("called {0:N}", totalCalls); 

    } 
} 

Sau khi để cho nó chạy trong khoảng 20 phút và hơn 30 triệu BeginInvoke gọi sau tiêu thụ bộ nhớ byte tin là hằng số (23 MB) cũng như các tính Xử lý. Có vẻ như không có rò rỉ. Tôi đã đọc cuốn sách của Jeffry Richters C# thông qua CLR, nơi ông nói rằng có một rò rỉ bộ nhớ. Ít nhất điều này dường như không còn đúng với .NET 3.5 SP1.

Môi trường thử nghiệm: Windows 7 x86 NET 3.5 SP1 Intel 6600 Dual Core 2.4 GHz

Yours, Alois Kraus

2

Trong một số trường hợp, BeginInvoke không cần EndInvoke (đặc biệt trong thông báo cửa sổ WinForms). Nhưng, chắc chắn có những tình huống mà vấn đề này - như BeginRead và EndRead cho giao tiếp không đồng bộ. Nếu bạn muốn làm một BeginWrite lửa và quên, có thể bạn sẽ gặp rắc rối nghiêm trọng về bộ nhớ sau một thời gian.

Vì vậy, một thử nghiệm của bạn không thể kết luận. Bạn cần phải đối phó với nhiều loại khác nhau của các đại biểu sự kiện không đồng bộ để đối phó với câu hỏi của bạn một cách chính xác.

+0

Bạn có lẽ đúng rằng một số hoạt động không đồng bộ thực sự yêu cầu một cuộc gọi đến EndInvoke, tôi đã nói về Delegate.BeginInvoke cụ thể. –

1

Hãy xem ví dụ sau, chạy trên máy của tôi trong vài phút và đạt được bộ làm việc 3,5 GB trước khi tôi quyết định tiêu diệt nó.

Action a = delegate { throw new InvalidOperationException(); }; 
while (true) 
    a.BeginInvoke(null, null); 

LƯU Ý: Đảm bảo chạy nó mà không cần trình gỡ rối đính kèm hoặc "ngắt ngoại lệ do người dùng ném" và "ngắt ngoại lệ do người dùng vô hiệu hóa".

EDIT: Khi Jeff chỉ ra, vấn đề bộ nhớ ở đây không phải là rò rỉ, nhưng đơn giản là trường hợp áp đảo hệ thống bằng cách xếp hàng nhanh hơn có thể xử lý. Thật vậy, các hành vi tương tự có thể được quan sát bằng cách thay thế ném với bất kỳ hoạt động phù hợp dài. Và việc sử dụng bộ nhớ bị ràng buộc nếu chúng ta để đủ thời gian giữa các cuộc gọi BeginInvoke.

Về mặt kỹ thuật, câu hỏi gốc chưa được trả lời. Tuy nhiên, bất kể có hay không nó có thể gây ra một rò rỉ, không gọi Delegate.EndInvoke là một ý tưởng tồi vì nó có thể khiến các ngoại lệ bị bỏ qua.

+1

Thú vị, nhưng bạn đã thử điều này chưa? Hành động a = delegate {throw new InvalidOperationException(); }; while (true) a.BeginInvoke (ar => { thử { a.EndInvoke (ar); } catch {}} , null); Mức tiêu thụ bộ nhớ giống nhau khi gọi EndInvoke, bộ nhớ có thể cao chỉ vì nó xếp hàng nhiều mục công việc trong hàng đợi hơn là có thể thực hiện chúng khi bạn ném ngoại lệ. –

+0

Bạn nói đúng. Tôi đã sửa chữa câu trả lời của mình. –

11

hay không nó hiện rò rỉ bộ nhớ không phải là điều bạn nên phụ thuộc vào . Nhóm khung công tác có thể, trong tương lai, thay đổi mọi thứ theo cách có thể gây ra rò rỉ và bởi vì chính sách chính thức là "bạn phải gọi EndInvoke", sau đó là "theo thiết kế".

Bạn có thực sự muốn mạo hiểm rằng ứng dụng của bạn đột nhiên sẽ bắt đầu rò rỉ bộ nhớ trong tương lai vì bạn đã chọn dựa vào hành vi được quan sát trên các yêu cầu được ghi lại?

+2

+1. Hơn nữa, không cần phải suy đoán về những nguy hiểm trong một bản sửa đổi tương lai của khuôn khổ. Như tôi đã chỉ ra trong câu trả lời của tôi, bỏ qua khả năng rò rỉ bộ nhớ, không gọi 'Delegate.EndInvoke' sẽ khiến các ngoại lệ bị bỏ qua và không cho bạn biết liệu hành động có được thực hiện thành công hay không. –

Các vấn đề liên quan