Lazy(Func<T>)的异常缓存问题
2021-04-15 00:00 萤火架构 阅读(186) 评论(0) 编辑 收藏 举报Lazy可以提供多线程环境下的安全保障,但是用不好也是会跳到坑里。
我这里使用Lazy<t>(Func<T>)来创建一个Lazy实例,然后在需要的地方访问它的Value属性,它可以保证在多线程环境下Func<T>仅执行一次,这看起来十分的美好:需要的时候执行,并且仅执行一次,再翻译下就是延迟加载,线程安全,资源消耗少。
问题
但是程序运行一段时间后出现了诡异的情况:出现一次异常后,程序不能自动恢复,一直抛出异常,直到程序重启,而出现异常的地方就在Func<T>中。
所有的好冥冥之中都是有代价的,查阅官方文档,发现Lazy会缓存异常。
Lazy<T>(Func<T>) 等同于 Lazy<T>(Func<T>, true) 或者 Lazy<T>(Func<T>,LazyThreadSafetyMode.ExecutionAndPublication),后边这两个构造函数的第二个参数的意思是在多线程环境下,委托只执行1次,使用这次的执行结果作为Lazy的值,同时如果委托中发生任何异常,都会被缓存下来。
官方还提供了一个例子可以验证异常缓存的问题,粘贴到这里:
using System; using System.Threading; class Program { static Lazy<LargeObject> lazyLargeObject = null; static LargeObject InitLargeObject() { return new LargeObject(); } static void Main() { // The lazy initializer is created here. LargeObject is not created until the // ThreadProc method executes. lazyLargeObject = new Lazy<LargeObject>(InitLargeObject); // The following lines show how to use other constructors to achieve exactly the // same result as the previous line: //lazyLargeObject = new Lazy<LargeObject>(InitLargeObject, true); //lazyLargeObject = new Lazy<LargeObject>(InitLargeObject, LazyThreadSafetyMode.ExecutionAndPublication); Console.WriteLine( "\r\nLargeObject is not created until you access the Value property of the lazy" + "\r\ninitializer. Press Enter to create LargeObject."); Console.ReadLine(); // Create and start 3 threads, each of which tries to use LargeObject. Thread[] threads = { new Thread(ThreadProc), new Thread(ThreadProc), new Thread(ThreadProc) }; foreach (Thread t in threads) { t.Start(); } // Wait for all 3 threads to finish. (The order doesn't matter.) foreach (Thread t in threads) { t.Join(); } Console.WriteLine("\r\nPress Enter to end the program"); Console.ReadLine(); } static void ThreadProc(object state) { try { LargeObject large = lazyLargeObject.Value; // IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the // object after creation. You must lock the object before accessing it, // unless the type is thread safe. (LargeObject is not thread safe.) lock(large) { large.Data[0] = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Initialized by thread {0}; last used by thread {1}.", large.InitializedBy, large.Data[0]); } } catch (ApplicationException aex) { Console.WriteLine("Exception: {0}", aex.Message); } } } class LargeObject { int initBy = 0; public int InitializedBy { get { return initBy; } } static int instanceCount = 0; public LargeObject() { if (1 == Interlocked.Increment(ref instanceCount)) { throw new ApplicationException("Throw only ONCE."); } initBy = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("LargeObject was created on thread id {0}.", initBy); } public long[] Data = new long[100000000]; } /* This example produces output similar to the following: LargeObject is not created until you access the Value property of the lazy initializer. Press Enter to create LargeObject. Exception: Throw only ONCE. Exception: Throw only ONCE. Exception: Throw only ONCE. Press Enter to end the program */
解决方案
在提出解决办法前,需要想一下,为什么会缓存异常?
因为要保证多线程环境下只执行一次,如果异常了还允许再次执行,就不能保证只执行一次了,而有些程序多次执行是不可行的。
来看几个解决方案:
1、不使用Lazy,自己加锁处理。
出现问题的程序中Lazy内部也是用了锁。
部分情况下可以用双检锁或则带升级的读写锁,以提高读的性能。
如果发生异常,可以抛到上层,并且再次获取时会重试执行。
2、使用Value时如果有异常,则重新给Lazy赋值。
不过这可能又要求赋值时线程安全。
3、如果经过评估可以多次创建Value,则可以更改线程安全模式为:LazyThreadSafetyMode.PublicationOnly
在这种模式下:多线程时每个线程都会创建,但是只使用第一个创建的,同时不缓存异常,异常发生后再次获取时会重新执行。
哪个适合自己,还需自己选择。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战