You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#控制台转服务:类自调用时如何保留Dictionary?

解决C# Windows服务中Dictionary状态保留的问题

看起来你现在的核心困扰是:每次定时器触发时都会新建Program实例,导致每个实例里的Dictionary都是全新的,之前的数据自然就丢失了。要搞定这个问题,关键是让所有Run方法调用共享同一个Dictionary实例,下面给你几个实用的方案:

方案一:用静态字段共享Dictionary

最简单直接的方式,就是把Program里的Dictionary改成静态字段,这样所有Program实例都会共用同一套数据:

public class Program
{
    // 静态线程安全字典:所有Program实例共享同一套数据
    private static readonly ConcurrentDictionary<string, object> _sharedData = new ConcurrentDictionary<string, object>();

    public void Run()
    {
        // 直接操作_sharedData,ConcurrentDictionary自带线程安全,无需手动加锁
        if (_sharedData.TryGetValue("LastExecution", out var lastTime))
        {
            Console.WriteLine($"上次执行时间:{lastTime}");
        }
        // 更新最新执行时间
        _sharedData["LastExecution"] = DateTime.Now;

        // 你的其他业务逻辑...
    }
}

这里我用了ConcurrentDictionary而非普通Dictionary,因为定时器的回调是在多线程环境下执行的,普通字典不是线程安全的,直接操作可能引发异常。ConcurrentDictionary专门为多线程场景设计,用起来更省心。

如果一定要用普通Dictionary,记得加锁保护操作:

private static readonly Dictionary<string, object> _sharedData = new Dictionary<string, object>();
private static readonly object _lock = new object();

public void Run()
{
    lock (_lock)
    {
        // 所有对_sharedData的操作都放在锁内
        if (!_sharedData.ContainsKey("LastExecution"))
        {
            _sharedData["LastExecution"] = DateTime.Now;
        }
        else
        {
            Console.WriteLine($"上次执行时间:{_sharedData["LastExecution"]}");
            _sharedData["LastExecution"] = DateTime.Now;
        }
    }
}

方案二:由Service类管理状态(更推荐)

静态字段虽然简单,但全局共享的方式不够灵活,也不利于后续测试。更好的做法是把Dictionary的生命周期交给Service类管理——Windows服务本身是单例运行的,服务启动后只会创建一个MyService实例,我们可以把字典存在这里,再传给Program:

第一步:修改Service类,管理共享状态

public class MyService : ServiceBase
{
    private Timer _timer;
    // 由Service持有共享字典
    private readonly ConcurrentDictionary<string, object> _sharedData = new ConcurrentDictionary<string, object>();
    // 只创建一次Program实例,避免重复初始化
    private readonly Program _program;

    public MyService()
    {
        // 把共享字典注入到Program中
        _program = new Program(_sharedData);
    }

    protected override void OnStart(string[] args)
    {
        // 初始化定时器,每小时执行一次
        _timer = new Timer(ExecuteTask, null, TimeSpan.Zero, TimeSpan.FromHours(1));
    }

    private void ExecuteTask(object state)
    {
        // 调用同一个Program实例的Run方法,共用同一个字典
        _program.Run();
    }

    protected override void OnStop()
    {
        _timer?.Dispose();
    }
}

第二步:修改Program类,接收注入的字典

public class Program
{
    private readonly ConcurrentDictionary<string, object> _sharedData;

    // 通过构造函数接收共享字典
    public Program(ConcurrentDictionary<string, object> sharedData)
    {
        _sharedData = sharedData;
    }

    public void Run()
    {
        // 直接操作注入的字典,数据会在多次Run调用中保留
        if (_sharedData.TryGetValue("Counter", out var countObj) && int.TryParse(countObj.ToString(), out int count))
        {
            count++;
            _sharedData["Counter"] = count;
            Console.WriteLine($"已执行{count}次");
        }
        else
        {
            _sharedData["Counter"] = 1;
            Console.WriteLine("首次执行");
        }

        // 你的其他业务逻辑...
    }
}

这种方式的好处是:状态的生命周期和服务绑定,逻辑更清晰,Program类不需要依赖静态字段,后续如果要扩展或者测试(比如替换字典实现)会方便很多。

额外提醒:持久化需求

如果你的Dictionary数据需要在服务重启后不丢失,光存在内存里是不够的,你需要把数据定期保存到文件、数据库或者配置文件中。比如每次Run结束后,把字典序列化为JSON保存到本地文件,服务启动时再读取回来:

// 保存数据到文件
File.WriteAllText("data.json", JsonSerializer.Serialize(_sharedData));

// 服务启动时读取数据
if (File.Exists("data.json"))
{
    var json = File.ReadAllText("data.json");
    var savedData = JsonSerializer.Deserialize<ConcurrentDictionary<string, object>>(json);
    // 把保存的数据合并到当前字典中
    foreach (var kvp in savedData)
    {
        _sharedData.TryAdd(kvp.Key, kvp.Value);
    }
}

这样即使服务重启,之前的数据也能恢复。

内容的提问来源于stack exchange,提问作者AliceSmith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:34:42