C#控制台转服务:类自调用时如何保留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

