当前位置: 首页 > 编程语言 > C#

System.Lazy有不同的线程安全模式分享

时间:2023-04-10 14:53:16 C#

System.Lazy有不同的线程安全模式。.NET4.0的System.Lazy类通过枚举LazyThreadSafetyMode提供了三种线程安全模式,我总结为:我想要一个遵循稍微不同的线程安全规则的惰性初始化值,即:只有一个并发线程会尝试创建底层值.成功创建后,所有等待的线程将获得相同的值。如果在创建过程中发生未处理的异常,它将在每个等待线程上重新抛出,但不会被缓存,后续访问基础值的尝试将重试创建并可能成功。因此,与LazyThreadSafetyMode.ExecutionAndPublication的主要区别在于,如果“提前”创建失败,可以稍后重试。是否存在提供这些语义的现有(.NET4.0)类,还是我必须自己编写?如果我自己推出,是否有一种聪明的方法可以在实现中重用现有的Lazy以避免显式锁定/同步?注意对于一个用例,假设“创建”可能很昂贵并且容易出现间歇性错误,例如从远程服务器获取大量数据。我不希望有多个并发尝试来获取数据,因为它们可能全部失败或全部成功。但是,如果他们失败了,我希望能够稍后重试。我尝试了Darin的更新答案,没有我指出的竞争条件……警告,我不完全确定这最终会完全没有竞争条件。私人静态int服务员=0;privatestaticvolatileLazylazy=newLazy(GetValueFromSomewhere);publicstaticobjectValue{get{惰性currLazy=惰性;如果(currLazy.IsValueCreated)返回currLazy.Value;互锁。增量);尝试{返回lazy.Value;//不管它是什么,只要留下“服务员”……没有坏处。}catch{if(Interlocked.Decrement(refwaiters)==0)lazy=newLazy(GetValueFromSomewhere);扔;}}}更新:我发现在发布这个之后我发现了一个竞争条件。这种行为实际上应该是可以接受的,只要你能接受一个可能罕见的情况,即某个线程抛出一个异常,这个异常是在另一个线程已经从一个成功的快速惰性(未来)返回到请求将成功之后从一个慢速惰性观察到的).我想不出会导致比“这个线程在另一个线程产生成功结果后抛出异常”更糟糕的事件序列。Update2:声明lazy为volatile以确保所有读者立即看到保护覆盖。有些人(包括我自己)看到volatile并立即想到“好吧,这可能被错误地使用了”,而且他们通常是对的。这就是我在这里使用它的原因:在上面示例的事件序列中,如果t1定位在读取lazy.Value之前,t3仍然可以读取旧的lazy而不是lazy。值t1修改lazy以包括lazy1。volatile防止这种情况发生,以便下一次尝试可以立即开始。我还提醒自己为什么在我的脑海里说“低锁并发编程很难,只需使用C#lock语句!!!”我一直在写这个原始答案。Update3:只是更改了Update2中的一些文本以指出需要使用volatile的实际情况——这里使用的Interlocked操作显然是全栅栏而不是当今重要CPU架构上实现的半栅栏,因为我最初只是假设排序,所以volatile保护比我原先想象的要窄的部分。只有一个并发线程会尝试创建基础值。成功创建后,所有等待的线程将获得相同的值。如果在创建过程中发生未处理的异常,它将在每个等待线程上重新抛出,但不会被缓存,后续访问基础值的尝试将重试创建并可能成功。由于懒惰不支持,你可以尝试自己滚动它:privatestaticobjectsyncRoot=newobject();私有静态对象值=null;publicstaticobjectValue{get{if(value==null){lock(syncRoot){if(value==null){//只有一个并发线程会尝试创建基础值。//并且如果`GetTheValueFromSomewhere`抛出异常,则值字段//不会分配给任何内容,稍后访问Value属性将重试。就异常//而言,它显然会被传播//给Valuegetter的使用者value=GetTheValueFromSomewhere();}}}返回值;}}更新:为了满足您对传输到所有等候阅读者线路程序的相同要求:privatestaticLazylazy=newLazy(GetTheValueFromSomewhere);publicstaticobjectValue{get{try{returnlazy.Value;}catch{//我们重新创建惰性字段,以便后续读者//不只是获得缓存的异常,而是尝试调用GetTheValueFromSomewhere()昂贵的method//为了再次计算值lazy=newLazy(GetTheValueFromSomewhere);//重新抛出异常,以便所有阻塞的读取器线程//都会抛出完全相同的异常。扔;}}}Lazy不支持这个这是Lazy的设计问题,因为异常“缓存”意味着惰性实例不会总是提供真正的价值。这可能会导致应用程序由于网络问题等暂时性错误而永久关闭。那时候通常需要人为干预。我敢打赌这个地雷存在于相当多的.NET应用程序中……你需要编写自己的懒惰来执行此操作。或者,为此打开一个CoreFxGithub问题。部分灵感来自达林的回答,但试图让这个“等待线程的队列异常”和“重试”功能起作用:privatestaticTask_fetcher=null;私有静态对象_value=null;publicstaticobjectValue{get{if(_value!=null)return_value;//我们“锁定”然后vartcs=newTaskCompletionSource();vartsk=Interlocked.CompareExchange(ref_fetcher,tcs.Task,null);if(tsk==null)//我们赢得了设置任务的竞赛{try{varresult=newobject();//无论真正的、昂贵的操作是什么tcs.SetResult(result);_value=结果;返回结果;}catch(Exceptionex){Interlocked.Exchange(ref_fetcher,null);//我们失败了。让别人以后再试tcs.SetException(ex);扔;}}tsk.Wait();//其他人正在做这个工作returntsk.Result;我有点担心-任何人都可以看到以非显而易见的方式失败的明显匹配吗?类似这样的可能会有所帮助:以上是C#学习教程:System.Lazy有不同线程安全模式的分享,如果对大家有用,需要详细了解C#学习教程,希望大家多多关注-使用系统;使用系统线程;命名空间ADifferentLazy{//////与Lazy基本相同,采用ExecutionAndPublication的LazyThreadSafetyMode,但不缓存异常///publicclassLazyWithNoExceptionCaching{privateFuncvalueFactory;私有T值=默认值(T);私有只读对象lockObject=newobject();私人布尔初始化=假;privatestaticreadonlyFuncALREADY_INVOKED_SENTINEL=()=>default(T);publicLazyWithNoExceptionCaching(FuncvalueFactory){this.valueFactory=valueFactory;}publicboolIsValueCreated{get{返回已初始化;}}publicTValue{get{//模仿LazyInitializer.EnsureInitialized()的双重检查锁定,同时允许控制流在成功初始化时清除valueFactoryif(Volatile.Read(refinitialized))returnvalue;lock(lockObject){if(Volatile.Read(refinitialized))返回值;价值=价值工厂();Volatile.Write(ref初始化,true);}valueFactory=ALREADY_INVOKED_SENTINEL;返回值;}}}}本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如有转载请注明出处: