C# 系统惰性<;T>;具有不同的线程安全模式

C# 系统惰性<;T>;具有不同的线程安全模式,c#,.net-4.0,thread-safety,C#,.net 4.0,Thread Safety,.NET 4.0的类通过enum提供了三种线程安全模式,我将总结为: LazyThreadSafetyMode.无-非线程安全 LazyThreadSafetyMode.ExecutionAndPublication-只有一个并发线程将尝试创建基础值。成功创建后,所有等待的线程将收到相同的值。如果在创建过程中发生未经处理的异常,则会在每个等待的线程上重新引发该异常,并在每次后续尝试访问基础值时缓存和重新引发该异常 LazyThreadSafetyMode.PublicationOnly-多个并

.NET 4.0的类通过enum提供了三种线程安全模式,我将总结为:

  • LazyThreadSafetyMode.无-非线程安全
  • LazyThreadSafetyMode.ExecutionAndPublication-只有一个并发线程将尝试创建基础值。成功创建后,所有等待的线程将收到相同的值。如果在创建过程中发生未经处理的异常,则会在每个等待的线程上重新引发该异常,并在每次后续尝试访问基础值时缓存和重新引发该异常
  • LazyThreadSafetyMode.PublicationOnly-多个并发线程将尝试创建基础值,但第一个成功的线程将确定传递给所有线程的值。如果在创建过程中发生未经处理的异常,则不会缓存该异常,同时和后续访问基础值的尝试将重新尝试创建并可能成功
我想要一个延迟初始化值,它遵循稍微不同的线程安全规则,即:

只有一个并发线程将尝试创建基础值。成功创建后,所有等待的线程将收到相同的值。如果在创建过程中发生未经处理的异常,将在每个等待的线程上重新引发该异常,但不会缓存该异常,随后访问基础值的尝试将重新尝试创建&可能会成功

因此,LazyThreadSafetyMode.ExecutionAndPublication与的关键区别在于,如果创建时的“第一步”失败,可以在以后重新尝试

是否有一个现有的(.NET 4.0)类提供这些语义,或者我需要自己的类?如果我自己动手,是否有一种聪明的方法可以在实现中重用现有的延迟来避免显式锁定/同步



注意:对于一个用例,假设“创建”可能非常昂贵,并且容易出现间歇性错误,例如从远程服务器获取大量数据。我不想同时多次尝试获取数据,因为它们很可能都会失败或成功。但是,如果失败,我希望以后能够重试。

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;
            }
        }
    }
}