Cache.Add 绝对过期 - 是不是基于 UTC?

Posted

技术标签:

【中文标题】Cache.Add 绝对过期 - 是不是基于 UTC?【英文标题】:Cache.Add absolute expiration - UTC based or not?Cache.Add 绝对过期 - 是否基于 UTC? 【发布时间】:2010-12-13 21:33:53 【问题描述】:

Cache.Add 的示例使用DateTime.Now.Add 来计算过期时间,即它通过了:

 DateTime.Now.AddSeconds(60)

作为absoluteExpiration 参数的值。

我认为相对于DateTime.UtcNow 计算它会更正确 [因为如果夏令时在从现在到到期点之间的时间开始,则没有歧义]。

在引入DateTimeKind 之前,我已经猜到缓存管理中有一些丑陋的黑客可以让它在时间不是UTC 时间时做一些适当的事情。

在 .NET 2.0 及更高版本中,我猜它应该正确处理计算为 DateTime.UtcNow.AddSeconds(60)DateTime,因为它有 DateTime.Kind 用作推理中的输入。

多年来,我一直自信地使用DateTime.UtcNow 作为基础,但无法提出理由证明这绝对是正确的做法,因为没有任何内容指出该文档对4 年以上。

问题?

    尽管进行了大量的狂欢和谷歌搜索,但我无法从 MS 中找到任何关于此的权威讨论 - 任何人都可以找到有关此的内容吗? 为什么使用 UtcNow 不会更正确和/或更安全?

(是的,我可以仔细阅读来源和/或 Reflector 的来源,但我正在寻找一个完整的详细介绍!)

【问题讨论】:

【参考方案1】:

我前段时间reported this bug on Microsoft Connect,但由于无法修复而已关闭。

如果您在本地时间指定绝对过期时间,在 .NET 2.0 中仍然存在问题。

在夏令时结束后的一小时内,您的当地时间是不明确的,因此您可能会得到意想不到的结果,即绝对到期时间可能比预期的时间长一小时。

在欧洲,夏令时于 2009 年 10 月 25 日 02:00 结束。下面的示例说明,如果您在 01:59 将项目放入缓存中并过期 2 分钟,它将在缓存中保留 2 分钟。一小时两分钟。

DateTime startTime = new DateTime(2009, 10, 25, 1, 59,0);
DateTime endTime = startTime.AddMinutes(2);
// end time is two minutes after start time

DateTime startUtcTime = startTime.ToUniversalTime();
DateTime endUtcTime = endTime.ToUniversalTime();
// end UTC time is one hour and two minutes after start UTC time

Console.WriteLine("Start UTC time = " + startUtcTime.ToString());
Console.WriteLine("End UTC time = " + endUtcTime.ToString());

.NET 2.0 或更高版本的解决方法是按照 Ruben 指出的那样以 UTC 指定绝对过期时间。

Microsoft 可能会建议在示例中使用 UTC 来表示绝对过期,但我想这可能会造成混淆,因为此建议仅对 .NET 2.0 及更高版本有效。

编辑

来自cmets:

但只有在 转换发生在重叠期间。 实际占用的单次转换 地点是您提交物品的时间 缓存.添加

仅当您在夏令时结束时的一个模糊小时内将具有 AbsoluteExpiration 时间的项目插入缓存中时,才会出现此问题。

例如,如果您的本地时区是中欧(冬季 GMT+1,夏季 GMT+2),并且您在 2009 年 10 月 25 日 01:59:00 执行以下代码:

DateTime absoluteExpiration = DateTime.Now.AddMinutes(2);
Cache.Add(... absoluteExpiration ...)

那么该项目将在缓存中保留一小时两分钟,而不是您通常期望的两分钟。这对于一些对时间要求非常严格的应用程序(例如股票行情、航空公司出发板)来说可能是个问题。

这里发生的事情是(假设欧洲时间,但任何时区的原则都是相同的):

DateTime.Now = 2009-10-25 01:59:00 本地。 local=GMT+2,所以 UTC = 2009-10-24 23:59:00

.AddMinutes(2) = 2009-10-25 02:01:00 本地。本地 = GMT+1,所以 UTC = 2009-11-25 01:01:00

Cache.Add 在内部将过期时间转换为 UTC (2009-11-25 01:01:00),因此过期时间比当前 UTC 时间 (23:59:00) 提前一小时两分钟.

如果您使用 DateTime.UtcNow 代替 DateTime.Now,则缓存到期时间为两分钟(.NET 2.0 或更高版本):

DateTime absoluteExpiration = DateTime.UtcNow.AddMinutes(2);
Cache.Add(... absoluteExpiration ...)

来自cmets:

还是我错过了什么?

不,你不是。您的分析是正确的,如果您的应用程序对时间要求严格,并且在 DST 结束时的这段时间内运行,那么您使用 DateTime.UtcNow 是正确的。

鲁本回答中的陈述:

只要设置了您提供的时间的种类,您就可以安全使用

不正确。

【讨论】:

@Joe:我同意您在上面提出的案例是正确的。但是只有在重叠期间发生转换时才会发生曝光。实际发生的单一转换是当您使用 Cache.Add 提交项目时。我不确定您对 Jeff 的哪些观点是关于最佳政策是指定基于 Utc 的到期时间或文档应如何涵盖它 - 这不是我在回答 [对自己] 中提出的观点吗?还是我错过了什么? (我确实了解根本问题,这就是我首先提出这个问题的原因) @Joe: +1 re the link to the Connect item: 我同意这是一个文档错误 @Joe - 你错误地将一些事情归因于我的回答,所以我编辑了你的回复以修复它们(除此之外,很好的发现)。 MSDN 现在已经更新了此方法的文档,建议所有绝对过期时间都应该以 UTC 格式传递......另外,如果有人想知道,就像我一样:在查看在 ILSpy 中的代码,我可以确认 ToUniversalTime 方法足够聪明,可以首先检查时间是否为 UTC 格式,如果是,则将其原封不动地返回。因此,在传入之前将时间转换为 UTC 没有任何缺点。 在 .net 4.0 中,删除已达到或超过 AbsoluteExpirationTime 的项目的线程似乎仅适用于 LocalTime。【参考方案2】:

Cache.Add 将过期日期转换为 UTC,然后再存储。

来自 Reflector(为了便于阅读,我省略了大部分参数):

public object Add(... DateTime absoluteExpiration ...)

    DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
    return this._cacheInternal.DoInsert(... utcAbsoluteExpiration ...);

CacheExpires.FlushExpiredItems 中,utcAbsoluteExpirationDateTime.UtcNow 进行比较。正如Joe notes in his answer,当缓存项的添加和过期跨越夏令时结束时,这会导致意外行为。

【讨论】:

干得好!我认为它在 3.5SP1 上。关于 2.0 raw 和 1.0 的任何想法?当Kind==DateTimeKind.Local 时,DateTimeUtil.ConvertToUniversalTime 是否除了对DateTime.ToUniversalTime 进行简单的调用之外还有什么作用?是的,我应该加载反射器。加载中... 谢谢。实际上,我对 cahce 的内部运作有着持久的兴趣:***.com/questions/1434284/…。 :) 那实际上是 2.0 框架。其他的我还没看,但我等待你的调查结果! 啊,DateTime.ToUniversalTime 和朋友们有一堆技巧,只要你的手臂默默地解决问题(在没有 DateTime.Kind 的 1.x 中,我相信它更有趣 - IIRC它总是在调整,因此您在调用它时必须更加小心?)。【参考方案3】:

[从 Jeff Sternal 的回答中高度衍生的见解,我在不完整的付款中 +1 了:D]

似乎在 1.1 中(没有查看 1.0,但我认为它相似),在没有 DateTime.Kind 并且发布了具有 DateTime.Now 相对时间的示例的情况下,他们立即调用 ToUniversalTime() 感到很舒服.

因此...

    在 1.x 中,如果您 [API 用户] 使用 DateTime.UtcNow(并且在调用 Cache.Add 期间开始对 DST 敏感),您最终会一团糟

    在 2.0 [和 3.x] 中,只要在您提供的时间上设置了 Kind,您就可以安全使用 [如果您从 @987654327 获得时间,通常是这样@ 或 UtcNow]。 [查看 Joe 的 cmets 并回答完整的理由] 绝对更喜欢 UtcNow 作为 1 小时 DST 切换的模糊性。

    该示例保持原样,因此使用 1.x 的人不会被误导 [人们可以继续假装 DST 是一个古怪的边缘情况 :P]。 [同上,正如 Joe 指出的那样] 但这是一个非常值得商榷的立场,因为这可能会导致内容在缓存中多停留一个小时。

我仍然很想知道更多细节,包括来自任何小马的任何细节。

编辑:我从 Joe 的 cmets 中意识到,我没有明确指出如果使用 2.0 或更高版本,则使用 UtcNow 肯定更正确更正确,因为一个人面临风险在 DST“土拨鼠时间”期间,该项目被额外缓存一小时。我还认为 MS 文档应该指出这一事实(附带条件是他们需要提及这不适用于 1.1 [无论页面是否标记为 2.0+ 特定]。谢谢乔。

编辑:Noda Time 将有一个简洁的包装来使这个万无一失:D

【讨论】:

有意思,这个周末有时间我得仔细看看 1.1。 "n 2.0 ...您可以安全地使用任何一个" - 不。本地时间在 DST 结束时每年有一个小时是不明确的 - 所以如果你在这个小时内指定一个本地时间,你可能会离开一个小时。看我的回答。 @Joe:AIUI 唯一的问题是当您放置一个本地时间为 1:59:59.999 的 absoluteExpiration 条目并且 Cache.Add [间接] 在切换发生后进行转换。在所有其他情况下,它知道您指定的时间的 DateTimeKind 并使用它来将时间转换(或不转换)为 Utc 以供其内部使用。你看过代码? "... ToUniversalTime ... 考虑到当前时差"。不,它考虑了与您要转换的日期时间相关的差异,而不是您调用 ToUniversalTime 时的当前差异。 “人们对 2:05 对人类的意义感到困惑”。机器也有歧义。以欧洲时间为例,DST 结束时的 02:05 可以是 00:05 UTC (GMT+2) 或 01:05 UTC (GMT+1)。当您将本地 02:05 转换为 UTC 时,它必须选择其中之一 - 实际上它选择了 01:05(没有 DST)。

以上是关于Cache.Add 绝对过期 - 是不是基于 UTC?的主要内容,如果未能解决你的问题,请参考以下文章

突然发现缓存这么好用

Cache和Session的区别

在HttpRuntime.Cache.Add中为空,值为null

ASP.net 缓存绝对过期不起作用

绝对良心的 Java 中发邮件功能

Redis 键的过期删除策略及缓存淘汰策略