5 kỹ thuật để tránh rò rỉ bộ nhớ bởi các sự kiện trong C # .NET bạn nên biết

  Đánh giá    Viết đánh giá
 0      22      0
Mã tài liệu xoaduq Danh mục Ngày đăng 22/4/2019 Tác giả Lần xem 22

Đăng ký sự kiện trong C # (và .NET nói chung) là nguyên nhân phổ biến nhất gây rò rỉ bộ nhớ. Ít nhất là từ kinh nghiệm của tôi. Trên thực tế, tôi đã thấy rất nhiều rò rỉ bộ nhớ từ các sự kiện mà việc nhìn thấy   mã = = ngay lập tức khiến tôi nghi ngờ.

Trong khi các sự kiện là tuyệt vời, chúng cũng nguy hiểm. Gây rò rỉ bộ nhớ là rất dễ dàng với các sự kiện nếu bạn không biết phải tìm gì. Trong bài đăng này, tôi sẽ giải thích nguyên nhân gốc rễ của vấn đề này và đưa ra một số kỹ thuật thực hành tốt nhất để giải quyết nó. Cuối cùng, tôi sẽ chỉ cho bạn một mẹo dễ dàng để tìm hiểu xem bạn có thực sự bị rò rỉ bộ nhớ hay không.

Hiểu rò rỉ bộ nhớ

Trong một môi trường rác được thu thập, thuật ngữ rò rỉ bộ nhớ là một chút truy cập trực quan. Làm thế nào bộ nhớ của tôi có thể bị rò rỉ khi có một người thu gom rác chăm sóc để thu thập mọi thứ?

Câu trả lời là với trình thu gom rác ( GC ), bộ nhớrò rỉcó nghĩa là có các đối tượng vẫn được tham chiếu nhưng không được sử dụng. Vì chúng được tham chiếu, nên GC sẽ không thu thập chúng và chúng sẽ tồn tại mãi mãi, chiếm bộ nhớ.

Hãy xem một ví dụ:

Trong ví dụ này, giả sử WiFiManager  tồn tại trong suốt vòng đời của chương trình. Sau khi thực hiện someOperation , một phiên bản của MyClass được tạo và không bao giờ được sử dụng lại. Các lập trình viên có thể nghĩ rằng GC sẽ thu thập nó, nhưng không phải như vậy. Các WiFiManager giữ một tham chiếu đến MyClass trong trường hợp nó  WiFiSignalChanged và nó gây ra rò rỉ bộ nhớ. GC sẽ không bao giờ thu thập MyClass .

1. Đảm bảo hủy đăng ký

Giải pháp rõ ràng (mặc dù không phải lúc nào cũng dễ nhất) là nhớ hủy đăng ký xử lý sự kiện của bạn khỏi sự kiện. Một cách để làm điều đó là triển khai IDis Dùng:

Tất nhiên, bạn phải đảm bảo gọi Dispose . Nếu bạn có WPF Control, một giải pháp dễ dàng là hủy đăng ký trong sự kiện Unloaded .

Ưu điểm : Đơn giản, mã dễ đọc.

Nhược điểm: Bạn có thể dễ dàng quên hủy đăng ký hoặc không hủy đăng ký trong mọi trường hợp dẫn đến rò rỉ bộ nhớ.

LƯU Ý: Không phải tất cả các đăng ký sự kiện gây rò rỉ bộ nhớ. Khi đăng ký một sự kiện mà bạn tồn tại lâu hơn , sẽ không có rò rỉ bộ nhớ. Ví dụ: trong WPF UserControl, bạn có thể đăng ký vàosự kiện Nhấp chuột của Nút . Điều này là tốt và không cần hủy đăng ký vì Điều khiển người dùng là người duy nhất tham chiếu Nút đó. Khi không có ai tham khảo Điều khiển người dùng, cũng sẽ không có ai tham khảo Nút và GC sẽ thu thập cả hai.

2. Có trình xử lý tự hủy đăng ký

Trong một số trường hợp, bạn có thể muốn xử lý sự kiện của mình xảy ra chỉ một lần. Trong trường hợp đó, bạn sẽ muốn mã tự hủy đăng ký. Khi trình xử lý sự kiện của bạn là một phương thức được đặt tên, nó đủ dễ dàng:

Tuy nhiên, đôi khi bạn muốn trình xử lý sự kiện của bạn là biểu thức lambda. Trong trường hợp đó, đây là một kỹ thuật hữu ích để tự hủy đăng ký:

Trong ví dụ trên, biểu thức lambda rất hữu ích vì bạn có thể nắm bắt biến cục bộ someObject , điều mà bạn không thể làm được với phương thức xử lý.

Ưu điểm: Đơn giản, dễ đọc, không có cơ hội rò rỉ bộ nhớ miễn là bạn chắc chắn sự kiện sẽ diễn ra ít nhất một lần.

Nhược điểm: Chỉ có thể sử dụng trong những trường hợp đặc biệt khi bạn cần xử lý sự kiện một lần.

3. Sử dụng các sự kiện yếu với bộ tổng hợp sự kiện

Khi bạn tham chiếu một đối tượng trong .NET, về cơ bản, bạn nói với GC rằng đối tượng đó đang được sử dụng, vì vậy đừng thu thập nó. Có một cách để tham chiếu một đối tượng mà không thực sự nói rằng tôi đang sử dụng nó. Loại tài liệu tham khảo này được gọi là  tài liệu tham khảo yếu . Thay vào đó, bạn đang nói rằng tôi không cần nó, nhưng nếu nó vẫn ở đó thì tôi sẽ sử dụng nó. Trongkháccác từ, nếu một đối tượng được tham chiếu bởi không có gì ngoài các tham chiếu yếu, thì GC sẽ thu thập nó và giải phóng bộ nhớ đó. Điều này được thực hiện bằng cách sử dụng  lớp WeakReference của .NET .

Chúng ta có thể sử dụng điều đó theo nhiều cách để ngăn chặn rò rỉ bộ nhớ. Một mẫu thiết kế phổ biến là sử dụng Bộ tổng hợp sự kiện . Khái niệm là bất kỳ ai cũng có thể đăng ký các sự kiện loại T và bất kỳ ai cũng có thể xuất bản các sự kiện loại T. Vì vậy, khi một lớp xuất bản sự kiện, tất cả các trình xử lý sự kiện đã đăng ký sẽ được gọi. Trình tổng hợp sự kiện tham chiếu mọi thứ bằng WeakReference. Vì vậy, ngay cả khi một đối tượngđây là đăng ký một sự kiện, nó vẫn có thể là rác được thu thập.

Dưới đây là một ví dụ sử dụng trình  tổng hợp sự kiện phổ biến của Prism (có sẵn với NuGet Prism.Core )

Ưu điểm:  Ngăn chặn rò rỉ bộ nhớ, tương đối dễ sử dụng.

Nhược điểm:  Hoạt động như một container toàn cầu cho tất cả các sự kiện. Bất cứ ai cũng có thể đăng ký với bất cứ ai khác. Điều này làm cho hệ thống khó hiểu khi sử dụng quá mức. Không tách rời mối quan tâm.

4. Sử dụng Trình xử lý sự kiện yếu với các sự kiện thông thường

Với một số nhào lộn mã, có thể sử dụng Tham chiếu yếu với các sự kiện thông thường. Điều này có thể đạt được theo nhiều cách khác nhau. Đây là một ví dụ sử dụng WeakEventHandler của Paul Stovell :

Tôi thực sự thích cách tiếp cận này vì nhà xuất bản, WiFiManager trong trường hợp của chúng tôi, được lưu giữ với các sự kiện C # tiêu chuẩn. Đây chỉ là một triển khai của mẫu này, nhưng thực sự có nhiều cách bạn có thể thực hiện. Daniel Grunwald đã viết một bài viết rộng rãi về các triển khai khác nhau và sự khác biệt của chúng.

Ưu điểm: Sử dụng các sự kiện tiêu chuẩn. Đơn giản. Không có rò rỉ bộ nhớ. Tách các mối quan tâm (không giống như Bộ tổng hợp sự kiện).

Nhược điểm: Việc triển khai khác nhau của mẫu này có sự tinh tế và các vấn đề khác nhau. Việc thực hiện trong ví dụ thực sự tạo ra một   đối tượng trình bao bọc đã đăng ký mà không bao giờ được thu thập bởi GC. Các hàm ý khác có thể giải quyết điều này, nhưng có các vấn đề khác như mã soạn sẵn bổ sung. Xem thêm thông tin về điều này trong bài viết của Daniel  .

Các vấn đề với các giải pháp WeakReference

Sử dụng WeakReference có nghĩa là GC sẽ có thể thu thập lớp đăng ký khi có thể. Tuy nhiên, GC không thu thập các đối tượng không được ước tính ngay lập tức. Nó làm ngẫu nhiên khi có liên quan đến các nhà phát triển. Vì vậy, với các sự kiện yếu, bạn có thể có các trình xử lý sự kiện được gọi trong các đối tượng không tồn tại tại thời điểm đó.

Trình xử lý sự kiện có thể làm một cái gì đó vô hại như cập nhật trạng thái bên trong. Hoặc nó có thể thay đổi trạng thái chương trình cho đến một lúc nào đó, GC quyết định thu thập nó. Loại hành vi này thực sự nguy hiểm. Đọc thêm về điều này trong  Mô hình sự kiện yếu là nguy hiểm .

5. Phát hiện rò rỉ bộ nhớ mà không cần cấu hình bộ nhớ

Kỹ thuật này là để kiểm tra rò rỉ bộ nhớ hiện có, thay vì các mẫu mã hóa để tránh chúng ở nơi đầu tiên.

Giả sử bạn nghi ngờ một lớp nào đó có rò rỉ bộ nhớ. Nếu bạn có một kịch bản trong đó bạn tạo một thể hiện và sau đó mong đợi GC thu thập nó, bạn có thể dễ dàng tìm hiểu xem các thể hiện của bạn sẽ được thu thập hay nếu bạn bị rò rỉ bộ nhớ. Thực hiện theo các bước sau:

1.Thêm Trình hoàn thiện vào lớp nghi ngờ của bạn và đặt điểm dừng bên trong:

2. Thêm 3 dòng ma thuật này sẽ được gọi vào đầu kịch bản:

Điều này sẽ buộc GC thu thập tất cả các trường hợp không được ước tính (không sử dụng trong sản xuất) cho đến nay, vì vậy chúng sẽ không can thiệp vào việc gỡ lỗi của chúng tôi.

3. Thêm 3 dòng mã ma thuật tương tự để chạy  sau kịch bản. Hãy nhớ rằng, kịch bản là một trong đó đối tượng nghi ngờ của bạn được tạo và nên được thu thập.

4. Chạy kịch bản trong câu hỏi.

Ở bước 1, tôi đã nói với bạn đặt một điểm dừng trong Finalizer của lớp. Bạn thực sự nên chú ý đến điểm dừng đó sau khi bộ sưu tập rác đầu tiên kết thúc. Nếu không, bạn có thể bị nhầm lẫn với các trường hợp cũ hơn được xử lý. Thời điểm quan trọng cần chú ý là liệu trình gỡ lỗi có dừng trong Finalizer  sau  kịch bản của bạn hay không. 

Nó cũng giúp đặt một điểm dừng trong hàm tạo của lớp. Bằng cách này, bạn có thể đếm được số lần nó được tạo so với số lần hoàn thành. Nếu điểm dừng trong bộ hoàn thiện được kích hoạt, thì GC đã thu thập cá thể của bạn và mọi thứ đều ổn. Nếu không, thì bạn bị rò rỉ bộ nhớ.

Đây là tôi gỡ lỗi một kịch bản sử dụng WeakEventHandler từ kỹ thuật cuối cùng và không bị rò rỉ bộ nhớ:

Đây là một kịch bản khác khi tôi sử dụng đăng ký sự kiện thông thường và nó bị rò rỉ bộ nhớ:

Tóm lược

Nó luôn làm tôi ngạc nhiên khi C # có vẻ như là một ngôn ngữ dễ học, với môi trường cung cấp các bánh xe đào tạo. nhưng trong thực tế, nó cách xa nó. Một điều đơn giản như sử dụng các sự kiện có thể dễ dàng biến ứng dụng của bạn thành một bó rò rỉ bộ nhớ bởi một bàn tay chưa được đào tạo.

Theo như mô hình chính xác để sử dụng trong mã, tôi nghĩ rằng kết luận từ bài viết này là không có câu trả lời đúng và sai cho tất cả các kịch bản. Tất cả các kỹ thuật được cung cấp và các biến thể củahọ,là những giải pháp khả thi tùy theo trường hợp. 

Đây hóa ra là một bài viết tương đối lớn, nhưng tôi vẫn ở mức tương đối cao với vấn đề này. Điều này chỉ chứng minh có bao nhiêu chiều sâu tồn tại trong những vấn đề này và cách phát triển phần mềm không bao giờ hết thú vị.

Để biết thêm thông tin về rò rỉ bộ nhớ, hãy xem bài viết của tôi Tìm, Khắc phục và Tránh Rò rỉ Bộ nhớ trong C # .NET: 8 Thực tiễn Tốt nhất . Nó có hàng tấn thông tin từ kinh nghiệm của riêng tôi và các nhà phát triển .NET cao cấp khác đã tư vấn cho tôi về nó. Nó bao gồm thông tin về Bộ nhớ cấu hình, rò rỉ bộ nhớ từ mã không được quản lý, bộ nhớ giám sát và hơn thế nữa.

Tôi muốn bạn để lại một số thông tin phản hồi trong phần bình luận. Và hãy chắc chắn đăng ký vào blog và được thông báo về bài viết mới.

Bạn phải gởi bình luận/ đánh giá để thấy được link tải

Nếu bạn chưa đăng nhập xin hãy chọn ĐĂNG KÝ hoặc ĐĂNG NHẬP

BÌNH LUẬN


Nội dung bậy bạ, spam tài khoản sẽ bị khóa vĩnh viễn, IP sẽ bị khóa.
Đánh giá(nếu muốn)
 BÌNH LUẬN

ĐÁNH GIÁ


ĐIỂM TRUNG BÌNH

0
0 Đánh giá
Tài liệu rất tốt (0)
Tài liệu tốt (0)
Tài liệu rất hay (0)
Tài liệu hay (0)
Bình thường (0)

Tài liệu tương tự

TÀI LIỆU NỔI BẬT