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

C#缓存变量对比及代理模式缓存失效问题求助(.NET 4.8)

.NET Framework 4.8 C#缓存变量比较与代理模式缓存问题排查

一、缓存中变量输入内容的比较方式

针对不同类型的变量,缓存中的内容比较逻辑如下:

  • 值类型(如DateTime、int、double):直接使用==运算符或Equals()方法即可,值类型默认比较的是内容本身。
  • 引用类型:
    • 若需比较引用地址:使用默认的==运算符(引用类型默认==比较对象引用)。
    • 若需比较内容:需重写类的Equals()和GetHashCode()方法,或实现IEquatable<T>接口,确保比较逻辑符合业务需求。
  • 字典缓存键的比较:Dictionary<TKey, TValue>默认使用EqualityComparer<TKey>.Default,值类型无需额外处理;自定义引用类型作为键时,必须确保其Equals和GetHashCode逻辑正确,否则会导致缓存命中失败。

二、代理模式缓存失效的Bug排查与修复

1. 最核心的Bug:代理实例重复创建

你的Main方法中,每次循环都会新建GoldServiceProxy实例,而缓存是代理类的私有字段,每次新实例的缓存都是空的,完全无法复用之前的查询结果。

修复方法:将代理实例移到循环外部,复用同一个实例的缓存:

static void Main(string[] args) 
{
    // 复用同一个代理实例,缓存才会生效
    GoldServiceProxy goldServiceProxy = new GoldServiceProxy(new GoldService());
    for (int i = 0; i < 10; i++)
    {
        try
        {
            // 注意取DateTime.Now.Date,避免时分秒导致同一日期被视为不同键
            var price = goldServiceProxy.get_price(DateTime.Now.Date);
            Console.WriteLine(price);
        }
        catch
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("Data nie została znaleziona");
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Wciśnij dowolny klawisz aby zakończyć");
            Console.ReadKey();
        }
    }
}

2. GoldService.get_price方法的致命问题

  • 方法参数DateTime date完全未被使用,而是从控制台读取日期,导致代理传入的缓存键和实际查询的日期不匹配,缓存逻辑彻底失效。
  • 方法始终返回0,缓存的都是无效值,毫无意义。
  • 用字符串拆分解析JSON的方式极易出错,应该使用JSON序列化库(如Newtonsoft.Json)进行解析。
  • 同步调用异步方法GetResponseAsync().Result容易引发死锁,建议改用async/await。

修复后的GoldService示例:

using System.Net.Http;
using Newtonsoft.Json;

public class GoldService
{
    private const string uriBase = "http://api.nbp.pl/api/cenyzlota/";
    private readonly HttpClient _httpClient = new HttpClient();

    public async Task<double> GetPriceAsync(DateTime date)
    {
        string dateStr = date.ToString("yyyy-MM-dd");
        string uriQuery = $"{uriBase}{dateStr}";

        var response = await _httpClient.GetAsync(uriQuery);
        response.EnsureSuccessStatusCode();

        string json = await response.Content.ReadAsStringAsync();
        var goldData = JsonConvert.DeserializeObject<List<GoldPriceDto>>(json);
        
        if (goldData == null || goldData.Count == 0)
            throw new InvalidOperationException("Nie znaleziono danych dla podanej daty");

        return goldData[0].Cena;
    }

    private class GoldPriceDto
    {
        [JsonProperty("cena")]
        public double Cena { get; set; }

        [JsonProperty("data")]
        public string Data { get; set; }
    }
}

3. 代理类的优化与修复

  • 同步方法改为异步,与服务类的方法签名匹配。
  • 缓存键使用日期的Date部分,避免时分秒导致相同日期被视为不同键。
  • 添加线程安全锁,防止多线程环境下的缓存竞争问题。

修复后的代理类示例:

internal class GoldServiceProxy
{
    private readonly GoldService _goldService;
    private readonly Dictionary<DateTime, double> _cache = new Dictionary<DateTime, double>();
    private readonly object _lockObj = new object();

    public GoldServiceProxy(GoldService goldService)
    {
        _goldService = goldService;
    }

    public async Task<double> GetPriceAsync(DateTime date)
    {
        var cacheKey = date.Date;
        lock (_lockObj)
        {
            if (_cache.TryGetValue(cacheKey, out var cachedPrice))
                return cachedPrice;
        }

        var price = await _goldService.GetPriceAsync(cacheKey);
        
        lock (_lockObj)
        {
            if (!_cache.ContainsKey(cacheKey))
                _cache.Add(cacheKey, price);
        }

        return price;
    }
}

4. 最终的Main方法

static async Task Main(string[] args)
{
    var goldServiceProxy = new GoldServiceProxy(new GoldService());
    for (int i = 0; i < 10; i++)
    {
        try
        {
            var price = await goldServiceProxy.GetPriceAsync(DateTime.Now);
            Console.WriteLine($"Cena złota: {price} zł");
        }
        catch (HttpRequestException ex)
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine($"Błąd połączenia: {ex.Message}");
        }
        catch (InvalidOperationException ex)
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine(ex.Message);
        }
        catch
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Nieoczekiwany błąd");
        }
        finally
        {
            Console.ResetColor();
        }
    }
    Console.WriteLine("Naciśnij dowolny klawisz aby zakończyć");
    Console.ReadKey();
}

三、缓存变量比较的关键注意事项

  • 日期类型作为缓存键时,务必使用Date属性忽略时分秒,避免同一日期因时间不同被视为不同键。
  • 自定义引用类型作为字典键时,必须重写Equals()和GetHashCode()方法,确保比较逻辑正确。
  • 多线程环境下操作缓存,必须添加线程锁,防止并发写入导致的缓存异常或数据不一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 22:31:15