内存监控对应解决方案

143 阅读11分钟

C# 中静态集合内存回收的实现方法

当发现静态集合(如 static List<T>static Dictionary<TKey, TValue>)占用大量内存时,需要确保集合中的对象在不再需要时能够被垃圾回收(GC)。以下是几种常见的实现方法:

1. 主动移除不再需要的对象

最直接的方法是在对象不再使用时,主动从静态集合中移除它们:

csharp

public static class CacheManager
{
    // 静态字典缓存对象
    private static readonly Dictionary<string, object> _cache = new Dictionary<string, object>();

    // 添加对象到缓存
    public static void AddToCache(string key, object value)
    {
        _cache[key] = value;
    }

    // 从缓存中移除对象
    public static void RemoveFromCache(string key)
    {
        if (_cache.ContainsKey(key))
        {
            _cache.Remove(key); // 移除引用,使对象可被GC回收
        }
    }

    // 批量清理缓存
    public static void ClearCache()
    {
        _cache.Clear(); // 清空集合,所有对象引用被移除
    }
}

2. 使用弱引用(WeakReference)

对于不需要强引用的对象,可以使用 WeakReference 包装:

csharp

public static class WeakCache
{
    private static readonly Dictionary<string, WeakReference<object>> _cache = 
        new Dictionary<string, WeakReference<object>>();

    public static void Add(string key, object value)
    {
        _cache[key] = new WeakReference<object>(value);
    }

    public static object Get(string key)
    {
        if (_cache.TryGetValue(key, out var weakRef) && weakRef.TryGetTarget(out var target))
        {
            return target;
        }
        // 对象已被GC回收
        _cache.Remove(key);
        return null;
    }
}

优点:当对象没有其他强引用时,即使仍在集合中,也会被 GC 回收。
缺点:需要处理对象已被回收的情况。

3. 实现缓存过期策略

为缓存对象设置过期时间,定期清理:

csharp

public class CachedItem
{
    public object Value { get; set; }
    public DateTime ExpirationTime { get; set; }
}

public static class TimeBasedCache
{
    private static readonly Dictionary<string, CachedItem> _cache = 
        new Dictionary<string, CachedItem>();

    public static void Add(string key, object value, TimeSpan expiration)
    {
        _cache[key] = new CachedItem
        {
            Value = value,
            ExpirationTime = DateTime.Now.Add(expiration)
        };
    }

    public static object Get(string key)
    {
        if (_cache.TryGetValue(key, out var item) && item.ExpirationTime > DateTime.Now)
        {
            return item.Value;
        }
        // 过期或不存在,移除
        _cache.Remove(key);
        return null;
    }

    // 定期清理过期项
    public static void Cleanup()
    {
        var expiredKeys = _cache
            .Where(kv => kv.Value.ExpirationTime <= DateTime.Now)
            .Select(kv => kv.Key)
            .ToList();

        foreach (var key in expiredKeys)
        {
            _cache.Remove(key);
        }
    }
}

4. 使用 ConcurrentDictionary + 弱引用

结合线程安全集合和弱引用:

csharp

public static class SafeWeakCache
{
    private static readonly ConcurrentDictionary<string, WeakReference<object>> _cache = 
        new ConcurrentDictionary<string, WeakReference<object>>();

    public static void Add(string key, object value)
    {
        _cache[key] = new WeakReference<object>(value);
    }

    public static bool TryGet(string key, out object value)
    {
        value = null;
        if (_cache.TryGetValue(key, out var weakRef) && weakRef.TryGetTarget(out var target))
        {
            value = target;
            return true;
        }
        // 对象已被回收,尝试移除
        _cache.TryRemove(key, out _);
        return false;
    }
}

5. 使用 IDisposable 接口释放资源

对于需要手动释放的资源(如文件句柄、网络连接),实现 IDisposable 接口:

csharp

public class MyResource : IDisposable
{
    private bool _disposed = false;
    private readonly Stream _stream; // 示例资源

    public MyResource()
    {
        _stream = new FileStream("data.txt", FileMode.Open);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _stream?.Close(); // 释放托管资源
            }
            _disposed = true;
        }
    }
}

// 使用示例
public static class ResourceManager
{
    private static readonly List<MyResource> _resources = new List<MyResource>();

    public static void AddResource(MyResource resource)
    {
        _resources.Add(resource);
    }

    public static void Cleanup()
    {
        foreach (var resource in _resources)
        {
            resource.Dispose(); // 释放资源
        }
        _resources.Clear(); // 清空集合
    }
}

6. 内存压力大时主动触发 GC

在极端情况下,可以在清理集合后主动触发垃圾回收:

csharp

public static void PerformFullCleanup()
{
    // 清理集合
    CacheManager.ClearCache();
    
    // 建议GC进行回收(谨慎使用,可能影响性能)
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
}

最佳实践总结

  1. 避免静态集合无限增长:实现清理策略(如 LRU 缓存、时间过期)

  2. 优先使用弱引用:对于不需要强引用的对象

  3. 及时释放资源:实现 IDisposable 并在不再需要时调用 Dispose()

  4. 定期监控内存使用:通过性能指标(如内存占用、GC 频率)调整策略

  5. 谨慎触发 GC:主动调用 GC.Collect() 可能影响性能,仅在必要时使用

通过这些方法,可以有效控制静态集合的内存占用,避免内存泄漏。

大量 System.String 或 System.Byte[]

可能表明: ○ 日志或缓存未限制大小 ○ 频繁创建大字符串(如字符串拼接) ○ 未正确释放资源(如文件流、网络缓冲区)如何解决?

1. 日志或缓存未限制大小

问题表现

  • 日志文件或缓存持续增长,占用大量内存
  • 静态集合(如 List<string>Dictionary<string, byte[]>)无限添加元素

解决方案

(1)限制日志缓存大小

使用具有固定容量的集合(如 ConcurrentQueue + 限制大小):

csharp

public class BoundedLogger
{
    private readonly ConcurrentQueue<string> _logs = new ConcurrentQueue<string>();
    private readonly int _maxCapacity = 1000; // 最大日志条数

    public void Log(string message)
    {
        // 超出容量时移除最旧的日志
        if (_logs.Count >= _maxCapacity)
        {
            _logs.TryDequeue(out _);
        }
        _logs.Enqueue(message);
    }
}
(2)实现日志滚动机制

使用第三方库(如 NLog、Serilog)并配置日志滚动:

xml

<!-- NLog 配置示例 -->
<target xsi:type="File" 
        name="logfile" 
        fileName="logs/${shortdate}.log" 
        archiveAboveSize="10485760" <!-- 10MB -->
        maxArchiveFiles="5" />
(3)使用弱引用缓存

对于非关键缓存,使用 WeakReference 避免阻止 GC 回收:

csharp

private static readonly Dictionary<string, WeakReference<byte[]>> _cache = 
    new Dictionary<string, WeakReference<byte[]>>();

2. 频繁创建大字符串(如字符串拼接)

问题表现

  • 循环中使用 + 拼接字符串
  • 大量临时字符串对象被创建
  • 字符串操作导致频繁内存分配和垃圾回收

解决方案

(1)使用 StringBuilder 替代字符串拼接

csharp

// 低效方式
string result = "";
for (int i = 0; i < 1000; i++)
{
    result += i.ToString(); // 每次循环创建新字符串
}

// 高效方式
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i); // 减少内存分配
}
string result = sb.ToString();
(2)预分配 StringBuilder 容量

csharp

// 预估容量,减少扩容次数
StringBuilder sb = new StringBuilder(1000);
(3)使用字符串插值(但避免在循环中滥用)

csharp

// 单次插值高效
string message = $"Name: {name}, Age: {age}";

// 循环中仍需使用 StringBuilder
StringBuilder sb = new StringBuilder();
foreach (var item in items)
{
    sb.AppendLine($"Item: {item}");
}
(4)复用字符串

csharp

// 使用 String.Intern 复用相同内容的字符串
string key = String.Intern($"cache-key-{id}");

3. 未正确释放资源(如文件流、网络缓冲区)

问题表现

  • byte[] 长时间持有大缓冲区
  • 文件 / 网络流未及时关闭
  • MemoryStreamFileStream 等未使用 using 语句

解决方案

(1)使用 using 语句确保资源释放

csharp

// 自动释放 FileStream
using (FileStream fs = new FileStream("data.txt", FileMode.Open))
{
    byte[] buffer = new byte[1024];
    fs.Read(buffer, 0, buffer.Length);
    // 处理数据
} // 此处自动调用 fs.Dispose()
(2)复用缓冲区

使用 ArrayPool<T> 避免重复创建大数组:

csharp

// 从共享池中获取数组
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024 * 1024); // 1MB
try
{
    // 使用缓冲区
    using (FileStream fs = new FileStream("data.txt", FileMode.Open))
    {
        fs.Read(buffer, 0, buffer.Length);
    }
}
finally
{
    // 归还数组到池中
    ArrayPool<byte>.Shared.Return(buffer);
}
(3)限制缓冲区大小

csharp

// 避免创建过大的固定缓冲区
const int MaxBufferSize = 1024 * 1024; // 1MB
byte[] buffer = new byte[Math.Min(data.Length, MaxBufferSize)];
(4)使用 Stream.CopyToAsync 简化操作

csharp

using (FileStream source = new FileStream("source.txt", FileMode.Open))
using (FileStream destination = new FileStream("dest.txt", FileMode.Create))
{
    // 自动处理缓冲区和复制
    await source.CopyToAsync(destination);
}

4. 其他优化建议

(1)批量处理替代循环处理

csharp

// 低效:每次处理一行创建新字符串
foreach (string line in lines)
{
    ProcessLine(line);
}

// 高效:批量处理
ProcessLines(lines);

void ProcessLines(IEnumerable<string> lines)
{
    // 批量处理逻辑
}

(2)使用 Span 避免内存分配

csharp

// 处理字符串无需分配新内存
ReadOnlySpan<char> span = "Hello World".AsSpan();

(3)分析字符串来源

使用 dotnet-dump 分析具体哪些字符串占用内存:

plaintext

> dumpheap -type System.String -min 10000  # 查找长度超过10000的字符串
> gcroot <对象地址>  # 追踪字符串被谁引用

总结

问题原因解决方案
日志 / 缓存无限增长使用有界集合、实现滚动机制、弱引用缓存
频繁字符串拼接使用 StringBuilder、预分配容量、避免循环中创建新字符串
资源未释放使用 using 语句、复用缓冲区、限制缓冲区大小
大字符串 / 数组分析来源、优化算法、使用更高效的数据结构(如 Memory、Span)

通过这些优化,可以显著减少 System.StringSystem.Byte[] 的内存占用,提高应用程序性能和稳定性。

事件处理器泄漏

若看到 System.EventHandleruserEventHandler` 或自定义事件处理器数量异常,检查:

  • 是否存在未注销的事件订阅
  • 订阅者是否比发布者生命周期更长

1. 确保所有事件订阅都有对应的取消订阅

(1)使用标准模式实现事件订阅 / 取消订阅

csharp

public class Publisher
{
    public event EventHandler MyEvent;

    public void RaiseEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

public class Subscriber : IDisposable
{
    private readonly Publisher _publisher;

    public Subscriber(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.MyEvent += HandleEvent; // 订阅事件
    }

    private void HandleEvent(object sender, EventArgs e)
    {
        // 处理事件
    }

    // 实现 IDisposable 接口
    public void Dispose()
    {
        _publisher.MyEvent -= HandleEvent; // 取消订阅
    }
}

(2)使用 using 语句自动管理生命周期

csharp

public class AutoUnsubscribeSubscriber : IDisposable
{
    private readonly Publisher _publisher;

    public AutoUnsubscribeSubscriber(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.MyEvent += HandleEvent;
    }

    private void HandleEvent(object sender, EventArgs e)
    {
        // 处理事件
    }

    public void Dispose()
    {
        _publisher.MyEvent -= HandleEvent;
    }
}

// 使用示例
using (var subscriber = new AutoUnsubscribeSubscriber(publisher))
{
    // 订阅者在 using 块内有效
} // 离开 using 块时自动调用 Dispose() 取消订阅

2. 防止订阅者生命周期长于发布者

(1)使用弱事件模式

当订阅者生命周期可能长于发布者时,使用弱引用避免阻止 GC 回收:

csharp

public class WeakEventPublisher
{
    private readonly List<WeakReference<EventHandler>> _handlers = new List<WeakReference<EventHandler>>();

    public void Subscribe(EventHandler handler)
    {
        _handlers.Add(new WeakReference<EventHandler>(handler));
    }

    public void Unsubscribe(EventHandler handler)
    {
        _handlers.RemoveAll(w =>
        {
            if (w.TryGetTarget(out var target) && target == handler)
            {
                return true;
            }
            return false;
        });
    }

    public void RaiseEvent()
    {
        var deadHandlers = new List<WeakReference<EventHandler>>();

        foreach (var weakHandler in _handlers)
        {
            if (weakHandler.TryGetTarget(out var handler))
            {
                handler(this, EventArgs.Empty);
            }
            else
            {
                deadHandlers.Add(weakHandler);
            }
        }

        // 移除已被GC回收的处理程序
        foreach (var deadHandler in deadHandlers)
        {
            _handlers.Remove(deadHandler);
        }
    }
}

(2)使用框架提供的弱事件管理器

csharp

using System.Windows; // WPF 中的 WeakEventManager

public class MyWeakEventManager : WeakEventManager
{
    private MyWeakEventManager() { }

    public static void AddHandler(MySource source, EventHandler handler)
    {
        CurrentManager.ProtectedAddHandler(source, handler);
    }

    public static void RemoveHandler(MySource source, EventHandler handler)
    {
        CurrentManager.ProtectedRemoveHandler(source, handler);
    }

    private static MyWeakEventManager CurrentManager
    {
        get
        {
            var managerType = typeof(MyWeakEventManager);
            var manager = (MyWeakEventManager)GetCurrentManager(managerType);

            if (manager == null)
            {
                manager = new MyWeakEventManager();
                SetCurrentManager(managerType, manager);
            }

            return manager;
        }
    }

    protected override void StartListening(object source)
    {
        ((MySource)source).MyEvent += OnEvent;
    }

    protected override void StopListening(object source)
    {
        ((MySource)source).MyEvent -= OnEvent;
    }

    private void OnEvent(object sender, EventArgs e)
    {
        DeliverEvent(sender, e);
    }
}

3. 处理静态事件订阅

静态事件特别容易导致内存泄漏,因为它们的生命周期与应用程序相同:

csharp

public class StaticEventPublisher
{
    public static event EventHandler StaticEvent;

    public static void RaiseStaticEvent()
    {
        StaticEvent?.Invoke(null, EventArgs.Empty);
    }
}

public class StaticEventSubscriber : IDisposable
{
    public StaticEventSubscriber()
    {
        StaticEventPublisher.StaticEvent += HandleStaticEvent; // 订阅静态事件
    }

    private void HandleStaticEvent(object sender, EventArgs e)
    {
        // 处理事件
    }

    public void Dispose()
    {
        StaticEventPublisher.StaticEvent -= HandleStaticEvent; // 必须手动取消订阅
    }
}

4. 使用委托字段替代事件(谨慎使用)

直接使用委托字段需要更严格的访问控制,但可以避免一些事件相关的问题:

csharp

public class DelegatePublisher
{
    // 注意:直接使用委托字段需要谨慎控制访问
    public Action MyDelegate;

    public void RaiseEvent()
    {
        MyDelegate?.Invoke();
    }
}

public class DelegateSubscriber : IDisposable
{
    private readonly DelegatePublisher _publisher;

    public DelegateSubscriber(DelegatePublisher publisher)
    {
        _publisher = publisher;
        _publisher.MyDelegate += HandleEvent;
    }

    private void HandleEvent()
    {
        // 处理事件
    }

    public void Dispose()
    {
        _publisher.MyDelegate -= HandleEvent;
    }
}

5. 自动化检查工具

(1)使用内存分析器

  • dotnet-dump:分析堆转储,查找事件处理器引用链
  • JetBrains dotMemory:可视化事件订阅关系
  • Visual Studio 内存分析器:检查对象引用

(2)编写代码分析器

使用 Roslyn 分析器检测未配对的事件订阅 / 取消订阅:

csharp

// 示例:检测未调用 Dispose 的事件订阅
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class EventSubscriptionAnalyzer : DiagnosticAnalyzer
{
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => 
        ImmutableArray.Create(Rule);

    private static readonly DiagnosticDescriptor Rule =
        new DiagnosticDescriptor(
            "ES001",
            "Unpaired event subscription",
            "Event subscription without matching unsubscribe",
            "Usage",
            DiagnosticSeverity.Warning,
            isEnabledByDefault: true);

    public override void Initialize(AnalysisContext context)
    {
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.EnableConcurrentExecution();
        context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.AddAssignmentExpression);
    }

    private void AnalyzeNode(SyntaxNodeAnalysisContext context)
    {
        // 分析事件订阅代码
    }
}

总结

问题场景解决方案
常规事件订阅实现 IDisposable 接口,在 Dispose 中取消订阅
订阅者生命周期不确定使用弱事件模式(手动实现或使用框架提供的 WeakEventManager)
静态事件订阅确保在订阅者生命周期结束时取消订阅,考虑使用弱引用
复杂场景使用内存分析工具(如 dotnet-dump、dotMemory)定位泄漏点

通过遵循这些最佳实践,可以有效避免事件处理器导致的内存泄漏,确保应用程序的内存使用效率。

非托管资源泄漏

关注 System.Runtime.InteropServices.SafeHandle 或实现 IDisposable 的类型:

○ 检查是否使用 using 语句或手动调用 Dispose()

○ 是否有 Finalize 队列堆积(使用 dumpheap -finalizequeue 查看)

1. 实现 IDisposable 模式

(1)基本实现

csharp

public class MyResource : IDisposable
{
    private IntPtr _handle; // 非托管资源句柄
    private SafeHandle _safeHandle; // 安全句柄包装非托管资源
    private bool _disposed = false;

    public MyResource()
    {
        // 初始化非托管资源
        _handle = NativeMethods.AllocateResource();
        _safeHandle = new SafeFileHandle(_handle, true);
    }

    // 实现 IDisposable 接口
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // 告诉 GC 无需调用终结器
    }

    // 可重载的 Dispose 方法
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            // 释放托管资源
            if (disposing)
            {
                _safeHandle?.Dispose();
            }

            // 释放非托管资源
            if (_handle != IntPtr.Zero)
            {
                NativeMethods.FreeResource(_handle);
                _handle = IntPtr.Zero;
            }

            _disposed = true;
        }
    }

    // 终结器(仅在需要直接释放非托管资源时使用)
    ~MyResource()
    {
        Dispose(false);
    }

    // 确保资源在使用前未被释放
    private void EnsureNotDisposed()
    {
        if (_disposed)
        {
            throw new ObjectDisposedException(nameof(MyResource));
        }
    }
}

2. 使用 using 语句自动释放资源

(1)基本用法

csharp

using (var fileStream = new FileStream("data.txt", FileMode.Open))
{
    // 使用 fileStream 读取文件
    byte[] buffer = new byte[1024];
    fileStream.Read(buffer, 0, buffer.Length);
} // 此处自动调用 fileStream.Dispose()

(2)多资源嵌套

csharp

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    
    using (var command = new SqlCommand("SELECT * FROM Users", connection))
    {
        using (var reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                // 处理数据
            }
        } // 自动释放 reader
    } // 自动释放 command
} // 自动释放 connection

3. 处理 Finalize 队列堆积

(1)使用 SafeHandle 替代手动终结器

csharp

public class MySafeResource : IDisposable
{
    private SafeHandle _safeHandle;
    private bool _disposed = false;

    public MySafeResource()
    {
        // 使用 SafeHandle 包装非托管资源
        _safeHandle = new SafeFileHandle(NativeMethods.OpenFile(), true);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
            }
            _disposed = true;
        }
    }
}

(2)监控 Finalize 队列

使用 dumpheap -finalizequeue 命令查看等待终结的对象:

plaintext

> dumpheap -finalizequeue

如果发现大量对象在 Finalize 队列中,可能表示:

  • 资源未及时释放
  • 终结器执行耗时过长
  • 终结器中存在异常

4. 实现资源池复用资源

(1)使用 ObjectPool 复用对象

csharp

using Microsoft.Extensions.ObjectPool;

public class MyResourcePool
{
    private readonly ObjectPool<MyResource> _pool;

    public MyResourcePool()
    {
        var policy = new DefaultPooledObjectPolicy<MyResource>();
        _pool = ObjectPool.Create(policy);
    }

    public MyResource GetResource()
    {
        return _pool.Get();
    }

    public void ReturnResource(MyResource resource)
    {
        _pool.Return(resource);
    }
}

5. 自动化检查工具

(1)使用代码分析器检测未释放资源

csharp

// 使用 Roslyn 分析器检测未使用 using 的 IDisposable 对象
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class DisposableAnalyzer : DiagnosticAnalyzer
{
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => 
        ImmutableArray.Create(Rule);

    private static readonly DiagnosticDescriptor Rule =
        new DiagnosticDescriptor(
            "DI001",
            "Unused IDisposable",
            "IDisposable object is created but not disposed properly",
            "Usage",
            DiagnosticSeverity.Warning,
            isEnabledByDefault: true);

    public override void Initialize(AnalysisContext context)
    {
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.EnableConcurrentExecution();
        context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ObjectCreationExpression);
    }

    private void AnalyzeNode(SyntaxNodeAnalysisContext context)
    {
        // 分析对象创建表达式
        var objectCreation = (ObjectCreationExpressionSyntax)context.Node;
        var typeSymbol = context.SemanticModel.GetTypeInfo(objectCreation).Type as INamedTypeSymbol;

        if (typeSymbol != null && typeSymbol.AllInterfaces.Any(i => i.Name == "IDisposable"))
        {
            // 检查是否在 using 语句中或手动调用 Dispose()
            // ...
        }
    }
}

(2)使用内存分析工具

  • dotnet-dump:分析堆转储,查找未释放的 SafeHandle 对象
  • JetBrains dotMemory:检测 IDisposable 对象泄漏
  • Visual Studio 内存分析器:追踪资源生命周期

6. 异步资源管理

(1)实现 IAsyncDisposable 接口

csharp

public class MyAsyncResource : IAsyncDisposable
{
    private HttpClient _httpClient;
    private bool _disposed = false;

    public MyAsyncResource()
    {
        _httpClient = new HttpClient();
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore();
        GC.SuppressFinalize(this);
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (!_disposed)
        {
            if (_httpClient != null)
            {
                _httpClient.Dispose();
                _httpClient = null;
            }
            _disposed = true;
        }
    }

    ~MyAsyncResource()
    {
        // 同步释放资源的后备机制
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        // 同步释放资源的实现
    }
}

(2)使用 await using 语句

csharp

await using (var resource = new MyAsyncResource())
{
    await resource.DoSomethingAsync();
} // 自动调用 DisposeAsync()

总结

问题场景解决方案
资源未释放实现 IDisposable 接口,使用 using 语句或手动调用 Dispose ()
Finalize 队列堆积使用 SafeHandle 替代手动终结器,优化终结器逻辑
资源频繁创建销毁实现资源池复用资源(如 ObjectPool)
异步资源管理实现 IAsyncDisposable 接口,使用 await using 语句
自动化检测使用 Roslyn 分析器或第三方工具(如 SonarQube)

通过遵循这些最佳实践,可以有效避免非托管资源泄漏,提高应用程序的稳定性和性能。