C# và Mô hình Sự kiện Nhẹ

Vấn đề tiềm ẩn về rò rỉ bộ nhớ trong mô hình侦 nghe sự kiện thông thường

Sự kiện thông thường được sử dụng như source.event += listener.delegate. Tại đây, sự kiện giữ một tham chiếu mạnh đến đối tượng侦 nghe (listener). Nếu侦 nghe không cần theo dõi sự kiện nữa, cần phải gọi source.event -= listener.delegate để giải phóng tham chiếu. Nếu quên thực hiện việc này,侦 nghe sẽ bị ràng buộc bởi sự kiện, gây ra rò rỉ bộ nhớ.

⚠⚠Lời khuyên: Khi không cần theo dõi sự kiện, hãy hủy đăng ký.

Trường hợp áp dụng mô hình sự kiện nhẹ

  • Khi侦 nghe không biết khi nào cần hủy đăng ký
  • Khi nguồn sự kiện tồn tại lâu hơn侦 nghe
  • Khi chu trình sống của nguồn sự kiện và侦 nghe không rõ ràng Trong những trường hợp này, mô hình sự kiện nhẹ là một giải pháp lý tưởng.

Mục tiêu của mô hình sự kiện nhẹ là sử dụng một quản lý sự kiện trung gian (WeakEventManager), khi nguồn sự kiện kích hoạt, quản lý này sẽ chịu trách nhiệm truyền sự kiện đến các侦 nghe. Vì quản lý này sử dụng tham chiếu yếu, nên không ảnh hưởng đến việc thu gom rác của侦 nghe.

Ba cách thực hiện mô hình sự kiện nhẹ

  1. Sử dụng WeakEventManager<TEventSource, TEventArgs>
  2. Tự định nghĩa một WeakEventManager
  3. Sử dụng các quản lý sự kiện nhẹ có sẵn, như PropertyChangedEventManager,...

Để minh họa, chúng ta sẽ sử dụng sự kiện ColorChange. Trước tiên, định nghĩa sự kiện, nguồn sự kiện và đối tượng sự kiện:

using System;

namespace WeakEventDemo
{
    public class ColorChangedEventArgs : EventArgs
    {
        public int Color { get; set; }
    }

    public delegate void ColorChangeHandler(object sender, ColorChangedEventArgs e);

    public class ColorController
    {
        private static ColorController _instance;

        public static ColorController Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new ColorController();
                }
                return _instance;
            }
        }

        public event ColorChangeHandler ColorChanged;

        public void RaiseColorChangeEvent(int color)
        {
            var e = new ColorChangedEventArgs();
            e.Color = color;
            this.ColorChanged?.Invoke(this, e);
        }
    }
}

Tình huống khi sử dụng += nhưng quên sử dụng -=

  • Trong UserControl1, đăng ký theo dõi sự kiện ColorChanged của ColorController. Khi sự kiện được kích hoạt, một hộp thoại thông báo sẽ hiển thị.
  • Trong MainWindow,按钮 đầu tiên mở một cửa sổ con chứa UserControl1. -ButtonClick thứ hai sẽ kích hoạt sự kiện ColorChanged, và do UserControl1 đã đăng ký lắng nghe, nên hộp thoại sẽ xuất hiện. -Đóng cửa sổ con và nhấn nút kích hoạt sự kiện một lần nữa, quan sát xem có hộp thoại nào hiển thị không. -Bạn sẽ thấy rằng hộp thoại vẫn hiển thị! Tại sao? Có phải UserControl1 đã sống lại?

Logic của MainWindow

<Window
    x:Class="WeakEventDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WeakEventDemo"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="525"
    Height="350"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Grid>
        <StackPanel>
            <Button
                Padding="0,8"
                Click="ShowBtn_OnClick"
                Content="Mở một cửa sổ hiển thị侦 nghe sự kiện màu thay đổi" />
            <Rectangle Height="8" />
            <Button
                Padding="0,8"
                Click="RaiseBtn_OnClick"
                Content="Kích hoạt sự kiện màu thay đổi" />
        </StackPanel>
    </Grid>
</Window>
using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace WeakEventDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ShowBtn_OnClick(object sender, RoutedEventArgs e)
        {
            var newWindow = new Window();
            newWindow.Width = 200;
            newWindow.Height = 200;
            newWindow.Owner = this;
            newWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
            newWindow.Content = new UserControl1();
            newWindow.Show();
        }

        private void RaiseBtn_OnClick(object sender, RoutedEventArgs e)
        {
            ColorController.Instance.RaiseColorChangeEvent(22);
        }
    }
}

Logic của UserControl1

<UserControl
    x:Class="WeakEventDemo.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WeakEventDemo"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <Grid>
        <TextBlock
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Text="Đang侦 nghe sự kiện màu thay đổi" />
    </Grid>
</UserControl>
using System.Windows;
using System.Windows.Controls;

namespace WeakEventDemo
{
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            ColorController.Instance.ColorChanged += Instance_ColorChanged;
        }

        private void Instance_ColorChanged(object sender, ColorChangedEventArgs e)
        {
            MessageBox.Show($"Màu thay đổi sang {e.Color}");
        }
    }
}

Hiệu ứng khi chạy:

Qua phân tích, UserControl1 bị giữ tham chiếu bởi ColorChanged, gây ra rò rỉ bộ nhớ. Khi cửa sổ con bị đóng, UserControl1 không thể được giải phóng.

Cách sử dụng WeakEventManager<TEventSource, TEventArgs>

Để giải quyết vấn đề trên, chúng ta có thể sử dụng弱EventManager, chỉ cần sửa đổi UserControl1:

using System.Windows;
using System.Windows.Controls;

namespace WeakEventDemo
{
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            WeakEventManager<ColorController, ColorChangedEventArgs>.AddHandler(ColorController.Instance, nameof(ColorController.ColorChanged), Instance_ColorChanged);
        }

        private void Instance_ColorChanged(object sender, ColorChangedEventArgs e)
        {
            MessageBox.Show($"Màu thay đổi sang {e.Color}");
        }
    }
}

Hiệu ứng khi chạy:

Sau một khoảng thời gian, kích hoạt sự kiện ColorChange, sẽ không có phản hồi nào.

Tự định nghĩa WeakEventManager

Không sử dụng phiên bảneneric, chúng ta có thể kế thừa lớp abstract WeakEventManager để quản lý sự kiện và侦 nghe:

using System;
using System.Windows;

namespace WeakEventDemo
{
    public class ColorChangeWeakEventManager : WeakEventManager
    {
        protected override void StartListening(object source)
        {
            var eventSource = source as ColorController;
            if (eventSource != null)
            {
                eventSource.ColorChanged += EventSource_ColorChanged;
            }
        }

        private void EventSource_ColorChanged(object sender, ColorChangedEventArgs e)
        {
            this.DeliverEvent(sender, e);
        }

        protected override void StopListening(object source)
        {
            var eventSource = source as ColorController;
            if (eventSource != null)
            {
                eventSource.ColorChanged -= EventSource_ColorChanged;
            }
        }

        protected override ListenerList NewListenerList()
        {
            return new ListenerList();
        }

        public void AddListener(object source, IWeakEventListener listener)
        {
            base.ProtectedAddListener(source, listener);
        }

        public void RemoveListener(object source, IWeakEventListener listener)
        {
            base.ProtectedRemoveListener(source, listener);
        }
    }
}

侦 nghe cần implement接口 IWeakEventListener:

using System;
using System.Windows;
using System.Windows.Controls;

namespace WeakEventDemo
{
    public partial class UserControl1 : UserControl, IWeakEventListener
    {
        public UserControl1()
        {
            InitializeComponent();
            var colorWeakEventManager = new ColorChangeWeakEventManager();
            colorWeakEventManager.AddListener(ColorController.Instance, this);
        }

        public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if (managerType == typeof(ColorChangeWeakEventManager))
            {
                var colorChangeArgs = e as ColorChangedEventArgs;
                if (colorChangeArgs != null)
                {
                    MessageBox.Show($"Màu thay đổi sang {colorChangeArgs.Color}");
                    return true;
                }
            }
            return false;
        }
    }
}

Tạo WeakEventManager cho trường hợp đặc biệt

Nếu cần xử lý một loại sự kiện cụ thể, có thể tạo một quản lý sự kiện chuyên dụng. Ví dụ:

using System;

namespace WeakEventDemo
{
    public delegate void ColorChangeHandlerNew(int color);

    public static class ColorControllerNew
    {
        public static event ColorChangeHandlerNew ColorChanged;

        public static void RaiseColorChangedEvent(int color)
        {
            ColorChanged?.Invoke(color);
        }
    }
}

Quản lý sự kiện sẽ xử lý việc đăng ký và hủy đăng ký:

using System;
using System.Windows;

namespace WeakEventDemo
{
    public class SpecificColorEventManager : WeakEventManager
    {
        protected override void StartListening(object source)
        {
            ColorControllerNew.ColorChanged += EventSource_ColorChanged;
        }

        private void EventSource_ColorChanged(int color)
        {
            var args = new ColorChangedEventArgs { Color = color };
            this.DeliverEvent(null, args);
        }

        protected override void StopListening(object source)
        {
            ColorControllerNew.ColorChanged -= EventSource_ColorChanged;
        }

        protected override ListenerList NewListenerList()
        {
            return new ListenerList();
        }

        private static SpecificColorEventManager CurrentManager
        {
            get
            {
                var cachedManager = WeakEventManager.GetCurrentManager(typeof(SpecificColorEventManager)) as SpecificColorEventManager;
                if (cachedManager == null)
                {
                    cachedManager = new SpecificColorEventManager();
                    WeakEventManager.SetCurrentManager(typeof(SpecificColorEventManager), cachedManager);
                }
                return cachedManager;
            }
        }

        public static void AddListener(object source, IWeakEventListener listener)
        {
            CurrentManager.ProtectedAddListener(source, listener);
        }

        public static void RemoveListener(object source, IWeakEventListener listener)
        {
            CurrentManager.ProtectedRemoveListener(source, listener);
        }
    }
}

Tạo một lớp侦 nghe cơ sở cho các sự kiện tương tự:

using System;
using System.Windows.Controls;

namespace WeakEventDemo
{
    public class WeakEventControl : Border, IWeakEventListener
    {
        public WeakEventControl()
        {
            SpecificColorEventManager.AddListener(null, this);
        }

        public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if (managerType == typeof(SpecificColorEventManager))
            {
                if (e is ColorChangedEventArgs args)
                {
                    OnColorChanged(args.Color);
                }
                return true;
            }
            return false;
        }

        protected virtual void OnColorChanged(int color) { }
    }
}

Lớp侦 nghe cụ thể:

using System.Windows;
using System.Windows.Controls;

namespace WeakEventDemo
{
    public partial class UserControl1 : WeakEventControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        protected override void OnColorChanged(int color)
        {
            MessageBox.Show($"Màu thay đổi sang {color}");
        }
    }
}

Cách này có lợi thế là các sự kiện cùng loại được xử lý tập trung trong quản lý sự kiện, giúp giảm sự phụ thuộc giữa các thành phần.

Thẻ: C# Sự kiện Nhẹ Rò rỉ bộ nhớ Quản lý sự kiện

Đăng vào ngày 15 tháng 9 lúc 00:36