当前位置:首页 > C# > 正文

C#依赖注入中的单例模式(如何实现线程安全的单例服务)

在现代 C# 开发中,尤其是使用 .NET Core 或 .NET 5+ 构建应用程序时,依赖注入(Dependency Injection, DI) 已成为标准实践。而其中的 单例(Singleton) 生命周期模式因其全局唯一性和资源复用优势被广泛使用。但如果不注意线程安全问题,就可能引发难以排查的并发错误。

本文将手把手教你如何在 C# 依赖注入容器中正确注册和使用线程安全的单例服务,即使是编程新手也能轻松理解!

C#依赖注入中的单例模式(如何实现线程安全的单例服务) C#依赖注入 单例模式线程安全 .NET Core DI 服务生命周期管理 第1张

什么是依赖注入中的单例?

在 .NET 的依赖注入系统中,服务有三种生命周期:

  • Transient(瞬态):每次请求都创建新实例。
  • Scoped(作用域):每个 HTTP 请求(或作用域)内共享一个实例。
  • Singleton(单例):整个应用程序生命周期内只创建一次,所有请求共享同一个实例。

当你注册一个服务为 AddSingleton 时,DI 容器会确保该服务在整个应用运行期间只有一个实例。这非常适合缓存、配置管理器或日志记录器等场景。

为什么单例需要线程安全?

由于单例在整个应用中是共享的,多个线程(例如来自不同 HTTP 请求的线程)可能会同时访问它的成员变量。如果这些操作涉及写入或修改状态,就可能发生 竞态条件(Race Condition),导致数据不一致或程序崩溃。

因此,在设计单例服务时,必须考虑 C#依赖注入 环境下的 单例模式线程安全 问题。

示例:非线程安全的单例

下面是一个典型的非线程安全单例服务:

public class CounterService{    private int _count = 0;    public void Increment()    {        _count++; // 非原子操作!多线程下可能出错    }    public int GetCount() => _count;}

如果多个线程同时调用 Increment()_count++ 操作可能被中断,导致最终结果小于预期。

如何实现线程安全的单例服务?

有多种方式可以保证线程安全。以下是两种常用且简单的方法:

方法一:使用 lock 关键字

public class ThreadSafeCounterService{    private int _count = 0;    private readonly object _lock = new object();    public void Increment()    {        lock (_lock)        {            _count++;        }    }    public int GetCount()    {        lock (_lock)        {            return _count;        }    }}

通过 lock,我们确保同一时间只有一个线程能进入临界区,从而避免并发冲突。

方法二:使用 Interlocked 类(推荐用于简单操作)

using System.Threading;public class InterlockedCounterService{    private int _count = 0;    public void Increment()    {        Interlocked.Increment(ref _count);    }    public int GetCount() => Volatile.Read(ref _count);}

Interlocked.Increment 是原子操作,性能更高,适合简单的数值增减场景。

在 .NET Core 中注册单例服务

Program.cs(.NET 6+)或 Startup.cs 中注册你的线程安全单例:

// .NET 6+ 示例var builder = WebApplication.CreateBuilder(args);// 注册为单例builder.Services.AddSingleton<ThreadSafeCounterService>();var app = builder.Build();app.MapGet("/count", (ThreadSafeCounterService counter) =>{    counter.Increment();    return $"当前计数: {counter.GetCount()}";});app.Run();

这样,无论多少用户同时访问 /count 接口,计数器都能安全地工作。

最佳实践与总结

  • 尽量让单例服务 无状态(即不保存可变数据),从根本上避免线程安全问题。
  • 如果必须有状态,请使用 lockInterlockedConcurrentDictionary 等线程安全机制。
  • 避免在构造函数中执行耗时操作,因为单例在首次解析时就会初始化。
  • 理解 .NET Core DI 的服务生命周期,合理选择 Singleton、Scoped 或 Transient。

通过以上方法,你就能在 C# 项目中安全地使用 服务生命周期管理 中的单例模式,构建高性能且稳定的 Web 应用或后台服务。

记住:单例虽好,线程安全不可少!