3 回答

TA貢獻1883條經驗 獲得超3個贊
我嘗試的是達林更新后的答案版本,但沒有我指出的比賽條件...警告,我不確定這最終是否完全擺脫了比賽條件。
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;
}
}
}
更新:我以為發布此消息后發現了比賽情況。該行為實際上應該是可以接受的,只要您對一個罕見的情況感到滿意,即Lazy<T>在另一個線程已經從成功的快速返回之后,某個線程拋出了一個從慢速觀察到的異常Lazy<T>(將來的請求將全部成功)。
waiters = 0
t1:一直運行到Interlocked.Decrement(waiters= 1)之前
t2:進入并運行到Interlocked.Increment(waiters= 1)之前
t1:進行Interlocked.Decrement并準備覆蓋(waiters= 0)
t2:一直運行到Interlocked.Decrement(waiters= 1)之前
t1:lazy用新的覆蓋(稱為lazy1)(waiters= 1)
t3:進入并在lazy1(waiters= 2)處阻止
t2:是否執行Interlocked.Decrement(waiters= 1)
t3:從lazy1(waiters現在不相關)獲取并返回值
t2:拋出異常
我無法提出一系列導致比“該線程在另一個線程產生成功結果之后引發異常”更糟糕的事情的事件。
Update2:聲明lazy為volatile確保所有讀者立即都能看到受保護的覆蓋。有些人(包括我自己在內)看到volatile并立即想到“好吧,可能是使用不正確”,他們通常是正確的。這就是我在這里使用它的原因:在上面示例中的事件序列中,t3仍然可以讀取舊的,lazy而不是lazy1如果它位于lazy.Valuet1修改lazy為包含的那一刻之前lazy1。 volatile防止這種情況,以便下次嘗試可以立即開始。
我還提醒自己,為什么我腦子里有這樣的話:“低鎖并發編程很難,只需使用C#lock語句?。?!” 我一直在寫原始答案。
Update3:只是更改了Update2中的一些文本,指出了volatile必要的實際情況- Interlocked此處使用的操作顯然是在當今重要的CPU架構上全屏實現的,而不是我最初只是假設的半屏實現的,因此volatile保護的范圍比我原來想象的要窄得多。

TA貢獻1757條經驗 獲得超7個贊
只有一個并發線程將嘗試創建基礎值。創建成功后,所有等待線程將獲得相同的值。如果在創建過程中發生未處理的異常,它將在每個等待的線程上重新拋出,但不會被緩存,并且隨后嘗試訪問基礎值的嘗試將重試創建,并且可能會成功。
由于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;
}
}
}
- 3 回答
- 0 關注
- 1002 瀏覽
添加回答
舉報