2008-08-18 33 views
10

Tôi đã tạo một "hành vi đính kèm" trong ứng dụng WPF của mình, cho phép tôi xử lý phím Enter và chuyển sang điều khiển tiếp theo. Tôi gọi nó là EnterKeyTraversal.IsEnabled và bạn có thể thấy mã trên blog của tôi here.Ngăn chặn rò rỉ bộ nhớ với các hành vi được đính kèm

Mối quan tâm chính của tôi bây giờ là tôi có thể bị rò rỉ bộ nhớ, vì tôi đang xử lý sự kiện PreviewKeyDown trên UIElements và không bao giờ "hủy" sự kiện một cách rõ ràng.

Cách tiếp cận tốt nhất để ngăn chặn rò rỉ này (nếu thực sự có một)? Tôi có nên giữ một danh sách các yếu tố mà tôi đang quản lý hay không và mở khóa sự kiện PreviewKeyDown trong sự kiện Application.Exit? Có ai đã thành công với các hành vi kèm theo trong các ứng dụng WPF của riêng họ và đưa ra một giải pháp quản lý bộ nhớ thanh lịch không?

Trả lời

4

Tôi không đồng ý DannySmurf

Một số đối tượng bố trí WPF có thể làm tắc nghẽn bộ nhớ của bạn và làm cho ứng dụng của bạn thực sự chậm khi họ không thu gom rác thải. Vì vậy, tôi tìm thấy sự lựa chọn của các từ được chính xác, bạn đang rò rỉ bộ nhớ cho các đối tượng bạn không còn sử dụng. Bạn mong đợi các mục sẽ được thu thập rác, nhưng chúng không, bởi vì có một tham chiếu ở đâu đó (trong trường hợp này là từ một trình xử lý sự kiện).

Bây giờ cho một câu trả lời thật :)

tôi khuyên bạn nên đọc WPF Performance article on MSDN

Không Loại bỏ Event Handlers trên Objects thể Giữ Objects Alive

Các đại biểu rằng một đối tượng đi tới sự kiện của nó thực sự là tham chiếu đối tượng đó. Do đó, sự kiện xử lý sự kiện có thể giữ cho các đối tượng còn sống dài hơn so với dự kiến. Khi thực hiện sạch đối tượng đã đăng ký nghe sự kiện của đối tượng, đó là cần thiết để xóa đại biểu đó trước khi phát hành đối tượng. Giữ các đối tượng không cần thiết còn sống sẽ tăng mức sử dụng bộ nhớ của ứng dụng . Đây là đặc biệt đúng khi đối tượng là gốc của cây hợp lý hoặc cây hình ảnh .

Họ khuyên bạn nên nhìn vào Weak Event pattern

Một giải pháp khác sẽ được loại bỏ các xử lý sự kiện khi bạn đang thực hiện với một đối tượng. Nhưng tôi biết rằng với Thuộc tính được đính kèm có thể không phải lúc nào cũng rõ ràng ..

Hy vọng điều này sẽ hữu ích!

+2

Tôi bỏ phiếu xuống vì câu trả lời không liên quan gì đến câu hỏi thực tế được hỏi. Mọi người đều biết về các lỗ hổng của bộ nhớ bị rò rỉ và nguyên nhân của nó, đặc biệt là do xử lý sự kiện như bạn đã đề cập. Những gì @Matt ở đây muốn biết là làm thế nào để xử lý sự kiện một cách an toàn xử lý sự kiện khi sử dụng bên trong hành vi kèm theo. Tôi sẽ sớm trả lời câu hỏi này. –

0

Đảm bảo các yếu tố tham chiếu sự kiện có trong đối tượng mà chúng đang tham chiếu, như hộp văn bản trong điều khiển biểu mẫu. Hoặc nếu điều đó không thể ngăn cản được. Tạo một sự kiện tĩnh trên một lớp trợ giúp toàn cầu và sau đó theo dõi lớp trợ giúp toàn cầu cho các sự kiện. Nếu hai bước này không thể được thực hiện, hãy thử sử dụng một WeakReference, chúng thường hoàn hảo cho các tình huống này, nhưng chúng đi kèm với overhead.

1

@Nâng Vâng, điều có hành vi kèm theo là theo định nghĩa, chúng không có cùng đối tượng với các yếu tố có sự kiện bạn đang xử lý.

Tôi nghĩ câu trả lời nằm trong WeakReference bằng cách nào đó, nhưng tôi đã không thấy bất kỳ mẫu mã đơn giản nào để giải thích cho tôi. :)

0

Tôi vừa đọc bài đăng trên blog của bạn và tôi nghĩ bạn có một chút lời khuyên gây hiểu lầm, Matt. Nếu có bộ nhớ thực tế rò rỉ tại đây, thì đó là lỗi trong Khuôn khổ .NET và không phải thứ gì đó bạn nhất thiết có thể khắc phục trong mã của bạn.

Những gì tôi nghĩ rằng bạn (và áp phích trên blog của bạn) thực sự nói về ở đây không thực sự là một rò rỉ, nhưng thay vì tiêu thụ liên tục của bộ nhớ. Đó không phải là điều tương tự. Để được rõ ràng, bộ nhớ bị rò rỉ là bộ nhớ được dành riêng bởi một chương trình, sau đó bị bỏ rơi (tức là, một con trỏ bị treo lơ lửng), và sau đó không thể được giải thoát. Kể từ khi bộ nhớ được quản lý trong .NET, đây là lý thuyết không thể. Có thể, tuy nhiên, đối với một chương trình để dự trữ một số lượng ngày càng tăng của bộ nhớ mà không cho phép tài liệu tham khảo để nó đi ra khỏi phạm vi (và trở thành đủ điều kiện cho thu gom rác thải); tuy nhiên bộ nhớ đó không bị rò rỉ. GC sẽ trả lại cho hệ thống khi chương trình của bạn thoát.

So. Để trả lời câu hỏi của bạn, tôi không nghĩ bạn thực sự gặp vấn đề ở đây. Bạn chắc chắn không có một rò rỉ bộ nhớ, và từ mã của bạn, tôi không nghĩ rằng bạn cần phải lo lắng, như xa như tiêu thụ bộ nhớ đi một trong hai. Miễn là bạn đảm bảo rằng bạn không liên tục gán trình xử lý sự kiện đó mà không bao giờ bỏ gán nó (nghĩa là bạn chỉ từng đặt nó một lần hoặc xóa chính xác một lần cho mỗi lần bạn gán nó), bạn dường như đang làm, mã của bạn nên được tốt. Có vẻ như đó là lời khuyên rằng áp phích trên blog của bạn đang cố gắng cung cấp cho bạn, nhưng ông đã sử dụng công việc đáng báo động đó là "rò rỉ", đó là một từ đáng sợ, nhưng nhiều lập trình viên đã quên ý nghĩa thực sự của thế giới được quản lý; nó không áp dụng ở đây.

0

@Arcturus:

... làm tắc nghẽn bộ nhớ của bạn và làm cho ứng dụng của bạn thực sự làm chậm khi họ không thu gom rác thải.

Điều đó rõ ràng không rõ ràng và tôi không đồng ý. Tuy nhiên:

... bạn đang rò rỉ bộ nhớ đối tượng mà bạn không còn sử dụng ... vì có tham chiếu đến chúng.

"bộ nhớ được phân bổ cho một chương trình, và chương trình mà sau đó mất khả năng truy cập vào nó do lỗi chương trình logic" (Wikipedia, "Memory rò rỉ")

Nếu có một tài liệu tham khảo tích cực vào một đối tượng mà chương trình của bạn có thể truy cập, sau đó theo định nghĩa nó không bị rò rỉ bộ nhớ. Rò rỉ có nghĩa là đối tượng không còn truy cập được (cho bạn hoặc với Hệ điều hành/Khung) nữa và sẽ không được giải phóng cho toàn bộ phiên hiện tại của hệ điều hành. Đây không phải là trường hợp ở đây.

(Xin lỗi là một Nazi ngữ nghĩa ...có lẽ tôi là một trường học cũ, nhưng rò rỉ có ý nghĩa rất cụ thể. Mọi người có xu hướng sử dụng "rò rỉ bộ nhớ" những ngày này có nghĩa là bất cứ điều gì tiêu thụ 2KB bộ nhớ nhiều hơn họ muốn ...)

Nhưng tất nhiên, nếu bạn không phát hành một xử lý sự kiện, đối tượng nó gắn liền với sẽ không được giải phóng cho đến khi bộ nhớ của quá trình được thu hồi bởi bộ thu gom rác khi tắt máy. Nhưng hành vi này hoàn toàn được mong đợi, trái với những gì bạn có vẻ ngụ ý. Nếu bạn mong đợi một đối tượng được khai hoang, thì bạn cần xóa mọi thứ có thể giữ tham chiếu còn sống, bao gồm cả trình xử lý sự kiện.

+0

Rò rỉ có ý nghĩa cụ thể trong ngữ cảnh của ngôn ngữ được quản lý so với ngôn ngữ không được quản lý. Danh pháp được hiểu chung nói đến một đối tượng còn lại trong bộ nhớ lâu hơn tuổi thọ dự kiến ​​của nó như là một rò rỉ trong mã được quản lý. Bạn nói đúng rằng trong thế giới không được quản lý khi các ứng dụng không chạy trong chế độ được bảo vệ, bộ nhớ không được giải thoát trở lại hệ điều hành không thể được khai hoang, nhưng điều này không liên quan gì đến câu hỏi của OP và không đóng góp gì cho cuộc thảo luận. –

4

Vâng tôi biết rằng trong những ngày cũ Rò rỉ bộ nhớ là một chủ đề hoàn toàn khác. Nhưng với mã số quản lý, ý nghĩa mới cho thuật ngữ Memory Leak có thể thích hợp hơn ...

Microsoft thậm chí còn thừa nhận nó là một rò rỉ bộ nhớ:

Tại sao Thực hiện Pattern WeakEvent?

Lắng nghe các sự kiện có thể dẫn đến rò rỉ bộ nhớ . Kỹ thuật điển hình để nghe sự kiện là sử dụng cú pháp theo ngôn ngữ cụ thể mà gắn trình xử lý vào sự kiện trên nguồn . Ví dụ: trong C#, cú pháp là: source.SomeEvent + = new SomeEventHandler (MyEventHandler).

Kỹ thuật này tạo một tham chiếu mạnh mẽ từ nguồn sự kiện tới trình nghe sự kiện . Thông thường, việc đính kèm trình xử lý sự kiện cho người nghe gây ra người nghe có một đối tượng có tuổi thọ là chịu ảnh hưởng của đối tượng suốt đời cho nguồn (trừ khi xử lý sự kiện ). Nhưng trong một số trường hợp bạn có thể muốn đối tượng đời của nghe được điều khiển chỉ bằng yếu tố khác, chẳng hạn như liệu nó hiện thuộc về cây thị giác của ứng dụng, và không phải do đời của nguồn . Bất cứ khi nào tuổi thọ đối tượng nguồn kéo dài vượt quá tuổi thọ đối tượng của người nghe, mẫu sự kiện bình thường dẫn đến rò rỉ bộ nhớ : người nghe được giữ còn sống lâu hơn dự định.

Chúng tôi sử dụng WPF cho ứng dụng khách có ToolWindows lớn có thể được kéo thả, tất cả các công cụ tiện lợi và tương thích với XBAP .. Nhưng chúng tôi có cùng một vấn đề với một số công cụ không phải là rác thu thập .. Điều này là do thực tế là nó vẫn còn phụ thuộc vào người nghe sự kiện .. Bây giờ điều này có thể không phải là một vấn đề khi bạn đóng cửa sổ của bạn và tắt ứng dụng của bạn. Nhưng nếu bạn đang tạo ToolWindows rất lớn với rất nhiều lệnh, và tất cả các lệnh này được đánh giá lại nhiều lần, và mọi người phải sử dụng ứng dụng của bạn cả ngày .. Tôi có thể nói với bạn .. nó thực sự làm tắc nghẽn bộ nhớ của bạn và thời gian phản hồi của ứng dụng của bạn ..

Ngoài ra, tôi thấy dễ dàng hơn khi giải thích cho người quản lý của mình rằng chúng tôi bị rò rỉ bộ nhớ, hơn là giải thích cho anh ta rằng một số đối tượng không được thu gom rác do một số sự kiện cần dọn dẹp;)

-1

Vâng (bit quản lý) tôi chắc chắn có thể hiểu và thông cảm.

Nhưng bất cứ điều gì Microsoft gọi nó, tôi không nghĩ định nghĩa "mới" là phù hợp.Nó phức tạp, bởi vì chúng ta không sống trong một thế giới được quản lý 100% (mặc dù Microsoft thích giả vờ rằng chúng tôi làm, chính Microsoft cũng không sống trong một thế giới như vậy). Khi bạn nói rò rỉ bộ nhớ, bạn có thể có nghĩa là một chương trình tiêu thụ quá nhiều bộ nhớ (đó là định nghĩa của người dùng), hoặc tham chiếu được quản lý sẽ không được giải phóng cho đến khi thoát (như ở đây), hoặc tham chiếu không được quản lý lên (đó sẽ là một rò rỉ bộ nhớ thực), hoặc mã không được quản lý được gọi từ mã được quản lý là bộ nhớ rò rỉ (một rò rỉ thực sự khác).

Trong trường hợp này, rõ ràng những gì "rò rỉ bộ nhớ" có nghĩa là, mặc dù chúng tôi đang không chính xác. Nhưng nó rất buồn tẻ khi nói chuyện với một số người, những người gọi mỗi lần tiêu thụ quá mức, hoặc thất bại trong việc thu thập rò rỉ bộ nhớ; và nó bực bội khi những người này là lập trình viên, người được cho là biết rõ hơn. Điều quan trọng đối với các thuật ngữ kỹ thuật là có ý nghĩa rõ ràng, tôi nghĩ vậy. Việc gỡ lỗi dễ dàng hơn nhiều khi họ thực hiện.

Dù sao đi nữa. Không có nghĩa là biến điều này thành một cuộc thảo luận cổ tích về ngôn ngữ. Chỉ cần nói ...

0

Đúng đúng,

Bạn đang đúng tất nhiên .. Nhưng có một thế hệ hoàn toàn mới của các lập trình viên được sinh ra trong thế giới này sẽ không bao giờ liên lạc mã không được quản lý, và tôi tin rằng định nghĩa ngôn ngữ sẽ tái phát minh ra nó hơn và hơn nữa. Rò rỉ bộ nhớ trong WPF theo cách này khác với C/Cpp.

Hoặc khóa học cho người quản lý của tôi Tôi gọi nó là rò rỉ bộ nhớ .. cho đồng nghiệp của tôi, tôi gọi nó là một vấn đề hiệu suất!

Đề cập đến vấn đề của Matt, đó có thể là vấn đề hiệu suất mà bạn có thể cần phải giải quyết. Nếu bạn chỉ sử dụng một vài màn hình và bạn thực hiện những điều khiển màn hình đơn, bạn có thể không thấy vấn đề này ở tất cả;).

3

cuộc tranh luận triết học sang một bên, trong nhìn vào bài viết trên blog của OP, tôi không thấy bất kỳ sự rò rỉ ở đây:

ue.PreviewKeyDown += ue_PreviewKeyDown; 

Một tài liệu tham khảo khó ue_PreviewKeyDown được lưu trữ trong ue.PreviewKeyDown.

ue_PreviewKeyDown là phương pháp STATIC và không thể là GCed.

Không có tham chiếu cứng nào đến ue đang được lưu trữ, do đó không có nội dung nào ngăn không cho nó là GCed.

Vậy ... Rò rỉ ở đâu?

+2

Đây là một sai lầm phổ biến. ue.PreviewKeyDown + = ue_PreviewKeyDown giữ một tham chiếu mạnh mẽ đến ue và vì us_PreviewKeyDown là tĩnh và ue sẽ không bao giờ được thu thập. – SACO

+0

@SACO bạn có thể giải thích điều đó không? "Tham chiếu mạnh mẽ tới ue" ở đâu? Theo như tôi thấy, John hoàn toàn đúng, hoàn toàn không có rò rỉ bộ nhớ trong ví dụ ban đầu. 'ue.PreviewKeyDown - = ue_PreviewKeyDown' là không cần thiết. – Golvellius

+0

@Golvellius Tôi đã đăng câu trả lời nên giải thích ý kiến ​​của tôi. Tôi thực sự kiểm tra nó ngay bây giờ và phát hiện ra sẽ không có rò rỉ nếu ue_PreviewKeyDown là tĩnh. – SACO

1

Để giải thích nhận xét của tôi về bài đăng của John Fenton ở đây là câu trả lời của tôi. Và hãy xem ví dụ sau:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var a = new A(); 
     var b = new B(); 

     a.Clicked += b.HandleClicked; 
     //a.Clicked += B.StaticHandleClicked; 
     //A.StaticClicked += b.HandleClicked; 

     var weakA = new WeakReference(a); 
     var weakB = new WeakReference(b); 

     a = null; 
     //b = null; 

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

     Console.WriteLine("a is alive: " + weakA.IsAlive); 
     Console.WriteLine("b is alive: " + weakB.IsAlive); 
     Console.ReadKey(); 
    } 


} 

class A 
{ 
    public event EventHandler Clicked; 
    public static event EventHandler StaticClicked; 
} 

class B 
{ 
    public void HandleClicked(object sender, EventArgs e) 
    { 
    } 

    public static void StaticHandleClicked(object sender, EventArgs e) 
    { 
    } 
} 

Nếu bạn có

a.Clicked += b.HandleClicked; 

và thiết lập chỉ b để null cả tài liệu tham khảo weakA và weakB ở lại còn sống! Nếu bạn thiết lập chỉ là một null b vẫn còn sống nhưng không phải là một (mà chứng minh rằng John Fenton là sai nói rằng một tài liệu tham khảo cứng được lưu trữ trong nhà cung cấp sự kiện - trong trường hợp này a).

này dẫn tôi đến kết luận sai lầm rằng

a.Clicked += B.StaticHandleClicked; 

sẽ dẫn đến một sự rò rỉ, vì tôi mặc dù trường hợp của một sẽ được giữ bởi các handler tĩnh. Đây không phải là trường hợp (kiểm tra chương trình của tôi).Trong trường hợp xử lý sự kiện tĩnh hoặc các sự kiện, nó là một cách khác. Nếu bạn viết

A.StaticClicked += b.HandleClicked; 

tham chiếu sẽ được giữ lại b.

+0

Cảm ơn bạn đã đầu tư thời gian trả lời bình luận của tôi, NHƯNG: John Fenton không sai khi nói rằng tài liệu tham khảo cứng được lưu trữ trong nhà cung cấp sự kiện. Bằng cách đặt 'a' thành null, về cơ bản bạn sẽ loại bỏ tham chiếu" cứng "vì đối tượng bộ nhớ' a' trỏ tới là rác được thu thập sau đó. Và 'a.Clicked + = B.StaticHandleClicked;' chính xác là tình huống của OP - điều này không bao giờ có thể gây ra rò rỉ bộ nhớ, do đó câu hỏi của John Fenton _So ... Sự rò rỉ ở đâu? _. Chỉ có một cách khác, như bạn đã chỉ ra, nhưng đây không phải là tình huống mà OP đang gặp phải. – Golvellius