.NET Framework 4.8 和 .NET 5 之间使用 HashSet 的巨大性能差异

问题描述

我遇到的问题是应用程序很慢,因为有数千个处理程序附加到它的事件,而删除这些处理程序的速度非常慢。我的应用程序作为 .NET Framework 4.8 进程运行,

我编写了一个测试程序(但使用 .NET 5),其中我用自己的实现替换了事件,其中我使用 HashSet 来存储处理程序并自己实现事件的添加/删除处理程序。

我的测试应用程序运行良好,但将此解决方案应用于我的实际应用程序后,性能仍然很差。

我意识到运行时差异似乎是原因。以下示例程序运行速度很快(在 .NET 5 上大约 1 秒,但如果在 .NET Framework 4.8 应用程序中运行相同的代码,则运行几分钟):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace EventPerformaneMeasurement
{
    class Program
    {
        private static void Main()
        {
            var sw = new Stopwatch();
            var mc = new MyClass();
            var list = Enumerable.Range(1,1_000_000).Select(x => new PropertyChangedEventHandler((_,_) => { Console.WriteLine($"Handler {x} called"); })).ToList();
            
            sw.Restart();
            list.ForEach(handler=>mc.PropertyChangednew += handler); 
           
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            
            sw.Restart();
            list.ForEach(handler=>mc.PropertyChangednew -= handler); 
            
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
    }

    class MyClass
    {
        private HashSet<PropertyChangedEventHandler> _handlers = new();
        
        public event PropertyChangedEventHandler PropertyChangednew
        {
            add => _handlers.Add(value);
            remove => _handlers.Remove(value);
        }
    }
}

我创建了两个 .NET fiddle 来重现这种行为,.NET fiddle 要求我稍微更改示例代码的语法并将迭代次数从 1000000 减少到 15000,但性能差异仍然可见:>

我无法相信 .NET 5 和 .NET Framework 4.x 在 HashSet 性能方面存在如此大的差异。

有没有人更了解这种效果或者我可能卡在哪里?有没有办法用另一种方式在 .NET 4.8 中获得相同的性能

解决方法

在这种情况下,由于我迫不及待地将我们的产品切换到 .NET 5,因此我需要重构以不使用事件/委托,而是使用带有接口的 Oberserver 模式并添加 /删除侦听器方法。这样的观察者对象不会有这样弱的 GetHashCode 实现,所以 HashSet 将与这样的观察者对象一起执行......