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
中,utcAbsoluteExpiration
与DateTime.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?的主要内容,如果未能解决你的问题,请参考以下文章