Có cần phải xóa rõ ràng các trình xử lý sự kiện trong C # không


120

Tôi có một lớp học cung cấp một vài sự kiện. Lớp đó được khai báo trên toàn cục nhưng không được ổn định dựa trên khai báo toàn cục đó - nó được nâng cấp trên cơ sở khi cần thiết trong các phương thức cần nó.

Mỗi khi lớp đó là cần thiết trong một phương thức, nó sẽ được cài đặt sẵn và các trình xử lý sự kiện được đăng ký. Có cần thiết phải loại bỏ trình xử lý sự kiện một cách rõ ràng trước khi phương thức vượt ra khỏi phạm vi không?

Khi phương thức vượt ra khỏi phạm vi, đối tượng của lớp cũng vậy. Việc để lại các trình xử lý sự kiện đã đăng ký với phiên bản đó đang vượt ra ngoài phạm vi có ngụ ý về dấu chân bộ nhớ không? (Tôi tự hỏi liệu trình xử lý sự kiện có giữ cho GC không nhìn thấy cá thể lớp vì không còn được tham chiếu nữa hay không.)

Câu trả lời:


184

Trong trường hợp của bạn, mọi thứ đều ổn. Đó là đối tượng xuất bản các sự kiện giữ cho các mục tiêu của trình xử lý sự kiện hoạt động. Vì vậy, nếu tôi có:

publisher.SomeEvent += target.DoSomething;

sau đó publishercó một tham chiếu đến targetnhưng không phải theo chiều ngược lại.

Trong trường hợp của bạn, nhà xuất bản sẽ đủ điều kiện để thu thập rác (giả sử không có tham chiếu nào khác đến nó) vì vậy thực tế là nó có tham chiếu đến các mục tiêu xử lý sự kiện là không liên quan.

Trường hợp khó khăn là khi nhà xuất bản tồn tại lâu dài nhưng người đăng ký không muốn như vậy - trong trường hợp đó, bạn cần hủy đăng ký người xử lý. Ví dụ: giả sử bạn có một số dịch vụ truyền dữ liệu cho phép bạn đăng ký nhận thông báo không đồng bộ về các thay đổi băng thông và đối tượng dịch vụ truyền có tuổi thọ lâu dài. Nếu chúng tôi làm điều này:

BandwidthUI ui = new BandwidthUI();
transferService.BandwidthChanged += ui.HandleBandwidthChange;
// Suppose this blocks until the transfer is complete
transferService.Transfer(source, destination);
// We now have to unsusbcribe from the event
transferService.BandwidthChanged -= ui.HandleBandwidthChange;

(Bạn thực sự muốn sử dụng một khối cuối cùng để đảm bảo bạn không làm rò rỉ trình xử lý sự kiện.) Nếu chúng tôi không hủy đăng ký, thì khối BandwidthUIsẽ tồn tại ít nhất là miễn là dịch vụ chuyển.

Cá nhân tôi hiếm khi gặp phải điều này - thường nếu tôi đăng ký một sự kiện, mục tiêu của sự kiện đó tồn tại ít nhất là miễn là nhà xuất bản - ví dụ: một biểu mẫu sẽ tồn tại miễn là nút trên đó. Thật đáng để biết về vấn đề tiềm ẩn này, nhưng tôi nghĩ một số người lo lắng về nó khi họ không cần thiết, bởi vì họ không biết các tham chiếu sẽ đi theo con đường nào.

CHỈNH SỬA: Đây là để trả lời bình luận của Jonathan Dickinson. Đầu tiên, hãy xem các tài liệu cho Delegate.Equals (đối tượng) rõ ràng cung cấp hành vi bình đẳng.

Thứ hai, đây là một chương trình ngắn nhưng đầy đủ để cho thấy hoạt động hủy đăng ký:

using System;

public class Publisher
{
    public event EventHandler Foo;

    public void RaiseFoo()
    {
        Console.WriteLine("Raising Foo");
        EventHandler handler = Foo;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
        else
        {
            Console.WriteLine("No handlers");
        }
    }
}

public class Subscriber
{
    public void FooHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Subscriber.FooHandler()");
    }
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;
         publisher.RaiseFoo();
         publisher.Foo -= subscriber.FooHandler;
         publisher.RaiseFoo();
    }
}

Các kết quả:

Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers

(Đã thử nghiệm trên Mono và .NET 3.5SP1.)

Chỉnh sửa thêm:

Điều này để chứng minh rằng một nhà xuất bản sự kiện có thể được thu thập trong khi vẫn còn các tham chiếu đến người đăng ký.

using System;

public class Publisher
{
    ~Publisher()
    {
        Console.WriteLine("~Publisher");
        Console.WriteLine("Foo==null ? {0}", Foo == null);
    }

    public event EventHandler Foo;
}

public class Subscriber
{
    ~Subscriber()
    {
        Console.WriteLine("~Subscriber");
    }

    public void FooHandler(object sender, EventArgs e) {}
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;

         Console.WriteLine("No more refs to publisher, "
             + "but subscriber is alive");
         GC.Collect();
         GC.WaitForPendingFinalizers();         

         Console.WriteLine("End of Main method. Subscriber is about to "
             + "become eligible for collection");
         GC.KeepAlive(subscriber);
    }
}

Kết quả (trong .NET 3.5SP1; Mono có vẻ hoạt động hơi kỳ lạ ở đây. Sẽ xem xét điều đó một lúc):

No more refs to publisher, but subscriber is alive
~Publisher
Foo==null ? False
End of Main method. Subscriber is about to become eligible for collection
~Subscriber

2
Tôi đồng ý với điều này nhưng nếu có thể, bạn có thể giải thích ngắn gọn hoặc tốt hơn là tham khảo một ví dụ về ý bạn là "nhưng người đăng ký không muốn như vậy" không?
Peter McG

@Jon: Được đánh giá cao, nó không phổ biến nhưng như bạn nói, tôi đã thấy mọi người lo lắng về điều này một cách không cần thiết.
Peter McG

- = Không hoạt động. - = Sẽ dẫn đến một đại biểu mới và các đại biểu không kiểm tra sự bình đẳng bằng cách sử dụng phương thức đích, họ thực hiện một đối tượng.ReferenceEquals () trên đại biểu. Đại biểu mới không tồn tại trong danh sách: nó không có hiệu lực (và không gây ra lỗi một cách kỳ lạ).
Jonathan C Dickinson

2
@Jonathan: Không, các đại biểu kiểm tra sự bình đẳng bằng cách sử dụng phương pháp đích. Sẽ chứng minh trong một bản chỉnh sửa.
Jon Skeet

Tôi thừa nhận. Tôi đã nhầm lẫn với các đại biểu ẩn danh.
Jonathan C Dickinson

8

Trong trường hợp của bạn, bạn ổn. Ban đầu tôi đã đọc ngược câu hỏi của bạn, rằng một người đăng ký đã đi ra ngoài phạm vi, không phải nhà xuất bản . Nếu nhà xuất bản sự kiện vượt ra ngoài phạm vi, thì các tham chiếu đến người đăng ký (tất nhiên không phải bản thân người đăng ký!) Sẽ đi cùng với nó và không cần phải xóa chúng một cách rõ ràng.

Câu trả lời ban đầu của tôi là bên dưới, về điều gì sẽ xảy ra nếu bạn tạo một người đăng ký sự kiện và để nó vượt ra khỏi phạm vi mà không hủy đăng ký. Nó không áp dụng cho câu hỏi của bạn nhưng tôi sẽ để nó thay thế cho lịch sử.

Nếu lớp vẫn được đăng ký thông qua trình xử lý sự kiện, thì nó vẫn có thể truy cập được. Nó vẫn là một vật thể sống. Một GC theo dõi biểu đồ sự kiện sẽ thấy nó được kết nối. Có, bạn sẽ muốn xóa rõ ràng các trình xử lý sự kiện.

Chỉ vì đối tượng nằm ngoài phạm vi phân bổ ban đầu không có nghĩa là đối tượng đó là ứng cử viên cho GC. Miễn là tham chiếu trực tiếp vẫn còn, nó sẽ hoạt động.


1
Tôi không tin rằng bất kỳ việc hủy đăng ký nào là cần thiết ở đây - GC xem các tài liệu tham khảo từ nhà xuất bản sự kiện chứ không phải nó và đó là nhà xuất bản mà chúng tôi lo ngại ở đây.
Jon Skeet

@Jon Skeet: Bạn nói đúng. Tôi đọc ngược câu hỏi. Tôi đã sửa câu trả lời của mình để phản ánh thực tế.
Eddie
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.