C# 系统惰性<;T>;具有不同的线程安全模式
.NET 4.0的类通过enum提供了三种线程安全模式,我将总结为:C# 系统惰性<;T>;具有不同的线程安全模式,c#,.net-4.0,thread-safety,C#,.net 4.0,Thread Safety,.NET 4.0的类通过enum提供了三种线程安全模式,我将总结为: LazyThreadSafetyMode.无-非线程安全 LazyThreadSafetyMode.ExecutionAndPublication-只有一个并发线程将尝试创建基础值。成功创建后,所有等待的线程将收到相同的值。如果在创建过程中发生未经处理的异常,则会在每个等待的线程上重新引发该异常,并在每次后续尝试访问基础值时缓存和重新引发该异常 LazyThreadSafetyMode.PublicationOnly-多个并
- LazyThreadSafetyMode.无-非线程安全
- LazyThreadSafetyMode.ExecutionAndPublication-只有一个并发线程将尝试创建基础值。成功创建后,所有等待的线程将收到相同的值。如果在创建过程中发生未经处理的异常,则会在每个等待的线程上重新引发该异常,并在每次后续尝试访问基础值时缓存和重新引发该异常
- LazyThreadSafetyMode.PublicationOnly-多个并发线程将尝试创建基础值,但第一个成功的线程将确定传递给所有线程的值。如果在创建过程中发生未经处理的异常,则不会缓存该异常,同时和后续访问基础值的尝试将重新尝试创建并可能成功
注意:对于一个用例,假设“创建”可能非常昂贵,并且容易出现间歇性错误,例如从远程服务器获取大量数据。我不想同时多次尝试获取数据,因为它们很可能都会失败或成功。但是,如果失败,我希望以后能够重试。Lazy不支持此操作。这是Lazy的一个设计问题,因为异常“缓存”意味着Lazy实例不会永远提供真正的值。由于网络问题等暂时性错误,这可能导致应用程序永久关闭。那时通常需要人为干预 我打赌这个地雷存在于相当多的.NET应用程序中 要做到这一点,你需要自己写。或者,为此打开CoreFx Github问题 只有一个并发线程将尝试创建基础线程 价值成功创建后,所有等待的线程都将收到 相同的值。如果在创建过程中发生未处理的异常,它将 在每个等待的线程上重新抛出,但它不会被缓存和删除 后续访问基础值的尝试将重新尝试 创造&可能成功 由于Lazy不支持此功能,因此您可以尝试自行运行:
private static object syncRoot = new object();
private static object value = null;
public static object Value
{
get
{
if (value == null)
{
lock (syncRoot)
{
if (value == null)
{
// Only one concurrent thread will attempt to create the underlying value.
// And if `GetTheValueFromSomewhere` throws an exception, then the value field
// will not be assigned to anything and later access
// to the Value property will retry. As far as the exception
// is concerned it will obviously be propagated
// to the consumer of the Value getter
value = GetTheValueFromSomewhere();
}
}
}
return value;
}
}
更新: 为了满足您关于将相同异常传播到所有等待的读卡器线程的要求:
private static Lazy<object> lazy = new Lazy<object>(GetTheValueFromSomewhere);
public static object Value
{
get
{
try
{
return lazy.Value;
}
catch
{
// We recreate the lazy field so that subsequent readers
// don't just get a cached exception but rather attempt
// to call the GetTheValueFromSomewhere() expensive method
// in order to calculate the value again
lazy = new Lazy<object>(GetTheValueFromSomewhere);
// Re-throw the exception so that all blocked reader threads
// will get this exact same exception thrown.
throw;
}
}
}
private static Lazy Lazy=new Lazy(从某处获取值);
公共静态对象值
{
得到
{
尝试
{
返回lazy.Value;
}
抓住
{
//我们重新创建lazy字段,以便后续的读取器
//不要只获取缓存的异常,而要尝试
//调用GetTheValueFromSomewhere()方法
//为了再次计算该值
lazy=新的lazy(从某处获取值);
//重新抛出异常,以便所有被阻止的读卡器线程
//将引发完全相同的异常。
投掷;
}
}
}
部分灵感来源于,但试图让“异常造成的等待线程队列”和“重试”功能正常工作:
private static Task<object> _fetcher = null;
private static object _value = null;
public static object Value
{
get
{
if (_value != null) return _value;
//We're "locking" then
var tcs = new TaskCompletionSource<object>();
var tsk = Interlocked.CompareExchange(ref _fetcher, tcs.Task, null);
if (tsk == null) //We won the race to set up the task
{
try
{
var result = new object(); //Whatever the real, expensive operation is
tcs.SetResult(result);
_value = result;
return result;
}
catch (Exception ex)
{
Interlocked.Exchange(ref _fetcher, null); //We failed. Let someone else try again in the future
tcs.SetException(ex);
throw;
}
}
tsk.Wait(); //Someone else is doing the work
return tsk.Result;
}
}
私有静态任务_fetcher=null;
私有静态对象_值=null;
公共静态对象值
{
得到
{
如果(_值!=null)返回_值;
//那么我们就“锁定”了
var tcs=new TaskCompletionSource();
var tsk=Interlocked.compareeexchange(ref\u fetcher,tcs.Task,null);
if(tsk==null)//我们赢得了设置任务的竞赛
{
尝试
{
var result=new object();//不管实际的、昂贵的操作是什么
tcs.SetResult(结果);
_值=结果;
返回结果;
}
捕获(例外情况除外)
{
Interlocked.Exchange(ref _fetcher,null);//我们失败了。以后让其他人再试一次
tcs.SetException(ex);
投掷;
}
}
tsk.Wait();//其他人正在做这项工作
返回tsk.Result;
}
}
不过我有点担心——有人能在这里看到任何明显的比赛,它会以不明显的方式失败吗?我尝试的一个版本没有比赛条件,我。。。警告,我不能完全肯定这最终是完全免费的比赛条件
private static int waiters = 0;
private static volatile Lazy<object> lazy = new Lazy<object>(GetValueFromSomewhere);
public static object Value
{
get
{
Lazy<object> currLazy = lazy;
if (currLazy.IsValueCreated)
return currLazy.Value;
Interlocked.Increment(ref waiters);
try
{
return lazy.Value;
// just leave "waiters" at whatever it is... no harm in it.
}
catch
{
if (Interlocked.Decrement(ref waiters) == 0)
lazy = new Lazy<object>(GetValueFromSomewhere);
throw;
}
}
}
private static int waiters=0;
private static volatile Lazy Lazy=new Lazy(getvaluefromwhere);
公共静态对象值
{
得到
{
懒惰=懒惰;
if(currLazy.IsValueCreated)
返回curr.Value;
联锁。增量(参考等待者);
尝试
{
返回lazy.Value;
//不管是什么,只要把“服务员”留在那里就行了……没什么害处。
}
抓住
{
if(联锁减量(参考等待器)==0)
lazy=新的lazy(getvaluefromwhere);
投掷;
using System;
using System.Threading;
namespace ADifferentLazy
{
/// <summary>
/// Basically the same as Lazy with LazyThreadSafetyMode of ExecutionAndPublication, BUT exceptions are not cached
/// </summary>
public class LazyWithNoExceptionCaching<T>
{
private Func<T> valueFactory;
private T value = default(T);
private readonly object lockObject = new object();
private bool initialized = false;
private static readonly Func<T> ALREADY_INVOKED_SENTINEL = () => default(T);
public LazyWithNoExceptionCaching(Func<T> valueFactory)
{
this.valueFactory = valueFactory;
}
public bool IsValueCreated
{
get { return initialized; }
}
public T Value
{
get
{
//Mimic LazyInitializer.EnsureInitialized()'s double-checked locking, whilst allowing control flow to clear valueFactory on successful initialisation
if (Volatile.Read(ref initialized))
return value;
lock (lockObject)
{
if (Volatile.Read(ref initialized))
return value;
value = valueFactory();
Volatile.Write(ref initialized, true);
}
valueFactory = ALREADY_INVOKED_SENTINEL;
return value;
}
}
}
}