带小眼睛的睁开闭合图标

In my last post I talked about Caching and some of the stuff I've been doing to cache the results of a VERY expensive call to the backend that hosts my podcast.

在我的上一篇文章中,我谈到了缓存以及为缓存非常昂贵的对托管我的播客的后端的调用的结果而进行的一些工作。

As always, the comments are better than the post! Thanks to you, Dear Reader.

与往常一样,评论比帖子更好! 谢谢您,亲爱的读者。

The code is below. Note that the MemoryCache is a singleton, but within the process. It is not (yet) a DistributedCache. Also note that Caching is Complex(tm) and that thousands of pages have been written about caching by smart people. This is a blog post as part of a series, so use your head and do your research. Don't take anyone's word for it.

代码如下。 请注意,MemoryCache是​​单例,但在进程内。 它还不是DistributedCache。 还要注意,缓存是复杂的(tm),关于聪明人缓存的内容已经撰写了成千上万的页面。 这是系列文章的一部分,因此,请动脑筋并进行研究。 不要相信任何人的话。

Bill Kempf had an excellent comment on that post. Thanks Bill! He said:

Bill Kempf对那个帖子有很好的评论。 谢谢比尔! 他说:

The SemaphoreSlim is a bad idea. This "mutex" has visibility different from the state it's trying to protect. You may get away with it here if this is the only code that accesses that particular key in the cache, but work or not, it's a bad practice.As suggested, GetOrCreate (or more appropriate for this use case, GetOrCreateAsync) should handle the synchronization for you.

SemaphoreSlim是一个坏主意。 此“互斥体”的可见性不同于它试图保护的状态。 如果这是唯一访问缓存中特定键的代码,但您可能无法正常运行,那么不管用与否,这是一个坏习惯,如建议的那样,GetOrCreate(或更适合此用例的GetOrCreateAsync)应该处理为您同步。

My first reaction was, "bad idea?! Nonsense!" It took me a minute to parse his words and absorb. Ok, it took a few hours of background processing plus I had lunch.

我的第一个React是:“坏主意?胡说八道!” 我花了一点时间来解析他的话并全神贯注。 好的,花了几个小时进行后台处理,然后我吃了午饭。

Again, here's the code in question. I've removed logging for brevity. I'm also deeply not interested in your emotional investment in my brackets/braces style. It changes with my mood. ;)

同样,这是有问题的代码。 为了简洁起见,我删除了日志记录。 我也对您对我的方括号/括号样式的情感投入完全不感兴趣。 它随着我的心情而改变。 ;)

public class ShowDatabase : IShowDatabase{    private readonly IMemoryCache _cache;    private readonly ILogger _logger;    private SimpleCastClient _client;

    public ShowDatabase(IMemoryCache memoryCache,            ILogger<ShowDatabase> logger,            SimpleCastClient client){        _client = client;        _logger = logger;        _cache = memoryCache;    }

    static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);

    public async Task<List<Show>> GetShows() {        Func<Show, bool> whereClause = c => c.PublishedAt < DateTime.UtcNow;

        var cacheKey = "showsList";        List<Show> shows = null;

        //CHECK and BAIL - optimistic        if (_cache.TryGetValue(cacheKey, out shows))        {            return shows.Where(whereClause).ToList();        }

        await semaphoreSlim.WaitAsync();        try        {            //RARE BUT NEEDED DOUBLE PARANOID CHECK - pessimistic            if (_cache.TryGetValue(cacheKey, out shows))            {                return shows.Where(whereClause).ToList();            }

            shows = await _client.GetShows();

            var cacheExpirationOptions = new MemoryCacheEntryOptions();            cacheExpirationOptions.AbsoluteExpiration = DateTime.Now.AddHours(4);            cacheExpirationOptions.Priority = CacheItemPriority.Normal;

            _cache.Set(cacheKey, shows, cacheExpirationOptions);            return shows.Where(whereClause).ToList(); ;        }        catch (Exception e) {            throw;        }        finally {            semaphoreSlim.Release();        }    }}

public interface IShowDatabase {    Task<List<Show>> GetShows();}

SemaphoreSlim IS very useful. From the docs:

SemaphoreSlim非常有用。 从文档:

The System.Threading.Semaphore class represents a named (systemwide) or local semaphore. It is a thin wrapper around the Win32 semaphore object. Win32 semaphores are counting semaphores, which can be used to control access to a pool of resources.

System.Threading.Semaphore类代表一个名为(系统级)或局部信号。 它是Win32信号量对象的薄包装。 Win32信号量正在计数信号量,可用于控制对资源池的访问。

The SemaphoreSlim class represents a lightweight, fast semaphore that can be used for waiting within a single process when wait times are expected to be very short. SemaphoreSlim relies as much as possible on synchronization primitives provided by the common language runtime (CLR). However, it also provides lazily initialized, kernel-based wait handles as necessary to support waiting on multiple semaphores. SemaphoreSlim also supports the use of cancellation tokens, but it does not support named semaphores or the use of a wait handle for synchronization.

SemaphoreSlim类代表一个轻量级的,快速的信号可以被用于当等待时间预计是很短的一个进程中等待。 SemaphoreSlim尽可能依赖于公共语言运行库(CLR)提供的同步原语。 但是,它还根据需要提供延迟初始化的基于内核的等待句柄,以支持对多个信号量的等待。 SemaphoreSlim还支持使用取消令牌,但不支持命名信号量或使用等待句柄进行同步。

And my use of a Semaphore here is correct...for some definitions of the word "correct." ;) Back to Bill's wise words:

我在这里使用信号量是正确的……对于“正确”一词的某些定义。 ;)回到比尔的明智话:

You may get away with it here if this is the only code that accesses that particular key in the cache, but work or not, it's a bad practice.

如果这是唯一访问高速缓存中特定键的代码,那么您可能会在这里忽略它,但是不管是否成功,这都是一个不好的做法。

Ah! In this case, my cacheKey is "showsList" and I'm "protecting" it with a lock and double-check. That lock/check is fine and appropriate HOWEVER I have no guarantee (other than I wrote the whole app) that some other thread is also accessing the same IMemoryCache (remember, process-scoped singleton) at the same time. It's protected only within this function!

啊! 在这种情况下,我的cacheKey是“ showsList”,我正在通过锁定和仔细检查来“保护”它。 该锁定/检查是正确的并且适当的,但是我不能保证(除了我编写了整个应用程序之外)其他某个线程也在同时访问同一IMemoryCache(请记住,进程作用域单例)。 仅在此功能内受保护!

Here's where it gets even more interesting.

在这里,它变得更加有趣。

  • I could make my own IMemoryCache, wrap things up, and then protect inside with my own TryGetValues...but then I'm back to checking/doublechecking etc. 我可以制作自己的IMemoryCache,包装好东西,然后用自己的TryGetValues保护里面...但是我回到检查/仔细检查等地方。
  • However, while I could lock/protect on a key...what about the semantics of other cached values that may depend on my key. There are none, but you could see a world where there are.

    但是,虽然我可以锁定/保护某个键,但其他缓存值的语义可能会取决于我的键。 没有,但是您可以看到一个世界。

Yes, we are getting close to making our own implementation of Redis here, but bear with me. You have to know when to stop and say it's correct enough for this site or project BUT as Bill and the commenters point out, you also have to be Eyes Wide Open about the limitations and gotchas so they don't bite you as your app expands!

是的,我们即将在这里实现自己的Redis实现,但请耐心等待。 正如Bill和评论员所指出的那样,您必须知道何时停止并说这对本网站或项目BUT来说是足够正确的,您还必须对限制和陷阱大开眼界,以便在应用扩展时不会对您造成伤害!

The suggestion was made to use the GetOrCreateAsync() extension method for MemoryCache. Bill and other commenters said:

有人建议对MemoryCache使用GetOrCreateAsync()扩展方法。 比尔和其他评论者说:

As suggested, GetOrCreate (or more appropriate for this use case, GetOrCreateAsync) should handle the synchronization for you.

根据建议,GetOrCreate(或更适合该用例的GetOrCreateAsync)应为您处理同步。

Sadly, it doesn't work that way. There's no guarantee (via locking like I was doing) that the factory method (the thing that populates the cache) won't get called twice. That is, someone could TryGetValue, get nothing, and continue on, while another thread is already in line to call the factory again.

可悲的是,它不能那样工作。 不能保证(通过像我一样的锁定操作)工厂方法(填充缓存的东西)不会被调用两次。 也就是说,有人可以使用TryGetValue,什么也没得到,然后继续,而另一个线程已经在排队以再次调用工厂。

public static async Task<TItem> GetOrCreateAsync<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, Task<TItem>> factory){    if (!cache.TryGetValue(key, out object result))    {        var entry = cache.CreateEntry(key);        result = await factory(entry);        entry.SetValue(result);        // need to manually call dispose instead of having a using        // in case the factory passed in throws, in which case we        // do not want to add the entry to the cache        entry.Dispose();    }

    return (TItem)result;}

Is this the end of the world? Not at all. Again, what is your project's definition of correct? Computer science correct? Guaranteed to always work correct? Spec correct? Mostly works and doesn't crash all the time correct?

这是世界末日吗? 一点也不。 同样,您的项目对“正确”的定义是什么? 计算机科学正确吗? 保证始终正确工作? 规格正确吗? 通常都能正常工作,并且不会一直崩溃吗?

Do I want to:

我要:

  • Actively and aggressively avoid making my expensive backend call at the risk of in fact having another part of the app make that call anyway?

    积极主动地避免拨打我昂贵的后端电话,而冒着让应用程序的另一部分继续打电话的风险?

    • What I am doing with my cacheKey is clearly not a "best practice" although it works today.

      我跟我的cacheKey做显然不是一个“最佳实践”,尽管它今天的作品。

  • Accept that my backend call could happen twice in short succession and the last caller's thread would ultimately populate the cache.

    接受我的后端调用可能在短时间内连续发生两次,而最后一个调用者的线程最终将填充高速缓存。

    • My code would become a dozen lines simpler, have no process-wide locking, but also work adequately. However, it would be naïve caching at best. Even ConcurrentDictionary has no guarantees - "it is always possible for one thread to retrieve a value, and another thread to immediately update the collection by giving the same key a new value."

      我的代码将变得更简单,没有进程范围的锁定,但也可以正常工作。 但是,充其量只是幼稚的缓存。 甚至ConcurrentDictionary也无法保证- “一个线程总是有可能检索值,而另一个线程可以通过为同一个键赋予新值来立即更新集合。”

What a fun discussion. What are your thoughts?

多么有趣的讨论。 你都有些什么想法呢?

翻译自: https://www.hanselman.com/blog/eyes-wide-open-correct-caching-is-always-hard

带小眼睛的睁开闭合图标

带小眼睛的睁开闭合图标_睁大眼睛-正确的缓存总是很难相关推荐

  1. 你的眼睛已经睁开了!(转)

    <映像PHOTO>杂志--卷首语 那日松/文 贴者按:这张旧照片和弟弟的文章勾起了我许多往事的回忆,30年弹指一挥间,没想到我们也到回忆的年龄.记忆真是个奇怪的东西,年轻时你在拼命寻找它, ...

  2. vue 和 uniapp 的密码框小眼睛(通用)

    密码输入框的小眼睛适合Vue 或者 uniapp使用 步骤1动态绑定:type 步骤2在data里声明 Data(){ Return{ seen:'', pwdType: 'password', // ...

  3. Element的input密码框小眼睛(点击眼睛显示密码,再次点击图标隐藏密码)

    一.知识点: 带 icon 的输入框,可以通过 prefix-icon 和 suffix-icon 属性在 input 组件首部和尾部增加显示图标,也可以通过 slot 来放置图标. 因为图标涉及点击 ...

  4. svg爱心小熊眼睛睁开动画

    下载地址 svg爱心小熊眼睛睁开动画. dd:

  5. JS实现密码框小眼睛的显示与隐藏(使用字体图标)

    JS实现密码框小眼睛的显示与隐藏(使用字体图标) 前端学习路上的小练习,如若不喜,请勿喷. 眼睛使用的是 iconfont 阿里矢量图标库的内容 链接:https://www.iconfont.cn/ ...

  6. ie浏览器自带小眼睛是什么用的_如何取消禁用IE10密码输入框中的小眼睛功能

    在升级到Windows 8之后,就发现在应用中登入账户以及使用IE10浏览器(桌面版及Metro/Modern版)时,输入密码后,会在密码输入框右侧有一个小眼睛一样的"可视"按钮, ...

  7. win7文件夹图标中多了一把小锁打不开文件夹怎么办?

    win7文件夹图标中多了一把小锁打不开文件夹怎么办? 解决办法一:右击目录→取得管理员权限!该方法适用于win7旗舰版. 解决办法二:右击目录→属性→安全→高级→选择everyone→更改权限→勾上完 ...

  8. 点击小眼睛完成密码框的显示与隐藏睁眼和闭眼

    //当我点击小眼睛时private void eye() {img_login_eye.setOnClickListener( new View.OnClickListener() {@Overrid ...

  9. 小飞鱼通达二开 PHP使用钉钉接口发送带链接的卡片消息(代码)

    普通的钉钉消息,采用小飞鱼上一篇文章的方法进行发送即可,很多时候我们需要在发送消息的同时想让接收人看到消息后可以直接点击链接进入到应用内进行相关操作,这里我们就采用了卡片消息的方法,当然钉钉还提供有很 ...

最新文章

  1. javascript eval和JSON之间的联系
  2. numpy的深复制与浅复制的区别_浅谈数据备份与复制对于企业用户的区别
  3. 乡镇快递站20万入股50%,每天派件600,是否靠谱?
  4. C语言仅在函数中可见,如何学习C语言、入门C语言,看这篇就够了
  5. java httpclient 进度条_SpringBoot如何实现一个实时更新的进度条的示例代码
  6. 【CF gym 103260】40th Petrozavodsk Programming Camp, Day 5,2021.2.3 水题2题
  7. 安卓开发 底部导航图标切换时动画效果_体验安卓 10:好用百倍都不止!
  8. MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)
  9. 浏览器cookie被禁掉,该如何去解决
  10. 华为防火墙配置IPSEC实现二个站点间网络互通 隧道模式 CLI配置 (三)
  11. LeetCode0704-二分查找
  12. Jointly Embedding Knowledge Graphs and Logical Rules
  13. 【编程题】【Scratch四级】2021.03 程序优化
  14. 计算机的键盘组合件,电脑组合键盘快捷键大全
  15. 2022年施工员-市政方向-通用基础(施工员)考试题模拟考试平台操作
  16. pdf文件过大如何缩小上传
  17. 【matlab】自定义颜色和线形绘图
  18. Python爬虫学习笔记 (2) [初级] 初识 requests + bs4 + re
  19. 火箭军计算机网络技术就业方向,计算机系统结构专业就业方向
  20. 掰碎了的正则表达式 : Java 篇

热门文章

  1. Php部分常见问题总结 [ http://www.phpx.com/happy/top38769.html ]
  2. 没有谁是一座孤岛——《岛上书店》
  3. LeetCode659:分割数组为连续子序列
  4. 北航计算机组成原理课程设计-2020秋 PreProject-入门测试
  5. 1.详细了解计算机如何工作
  6. KepServer的下载安装与使用说明
  7. js日期加减一天_js 日期加减 (转)
  8. 示例 PHP开发编译自己的拓展
  9. 以太坊源码阅读2——RLP编码
  10. 马斯克新要求+1:写周报,具体到代码行数的那种……