c# - 具有不同线程安全模式的 System.Lazy<T>

标签 c# .net-4.0 thread-safety

.NET 4.0 的 System.Lazy<T>类通过枚举提供三种线程安全模式 LazyThreadSafetyMode ,我将总结为:

  • LazyThreadSafetyMode.None - 不是线程安全的。
  • LazyThreadSafetyMode.ExecutionAndPublication - 只有一个并发线程会尝试创建基础值。成功创建后,所有等待的线程都将收到相同的值。如果在创建期间发生未处理的异常,它将在每个等待的线程上重新抛出,缓存并在每次后续尝试访问基础值时重新抛出。
  • LazyThreadSafetyMode.PublicationOnly - 多个并发线程将尝试创建基础值,但第一个成功的线程将确定传递给所有线程的值。如果在创建过程中发生未处理的异常,它将不会被缓存,并且并发和后续访问基础值的尝试将重试创建并可能成功。

我想要一个惰性初始化值,它遵循稍微不同的线程安全规则,即:

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

因此,与 LazyThreadSafetyMode.ExecutionAndPublication 的主要区别在于,如果创建时“第一次尝试”失败,可以稍后重新尝试。

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


注意对于一个用例,想象一下“创建”可能很昂贵并且容易出现间歇性错误,例如涉及从远程服务器获取大量数据。我不想进行多次并发尝试来获取数据,因为它们很可能全部失败或全部成功。但是,如果它们失败了,我希望稍后能够重试。

最佳答案

Only one concurrent thread will attempt to create the underlying value. On successful creation, all waiting threads will receive the same value. If an unhandled exception occurs during creation, it will be re-thrown on each waiting thread, but it will not be cached and subsequent attempts to access the underlying value will re-try the creation & may succeed.

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

关于c# - 具有不同线程安全模式的 System.Lazy<T>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34529533/

相关文章:

java - 关于多线程编程的赛车场景

c# - 使用互操作在 excel 中设置数字格式

c# - ms ajax $find ('clientID' ) 不断出现错误

c# - 如何使用结构指针导入非托管dll?

.net - Axum vs TPL,异步

java - 即使主线程执行完成,Java应用程序如何继续运行?

c# - 将用户在文本框中输入的 URL 转换为 URI 格式

.net-4.0 - sos 和 clr 版本不匹配 - 如何解决

c# - TaskCreationOptions.LongRunning 选项和 ThreadPool

java - 如何同步对不同类成员的访问?