You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
9.0 KiB
305 lines
9.0 KiB
5 years ago
|
#if FAT
|
||
|
|
||
|
using System;
|
||
|
using System.Threading;
|
||
|
|
||
|
using LinqInternal.Core;
|
||
|
|
||
|
namespace LinqInternal.Threading.Needles
|
||
|
{
|
||
|
[Serializable]
|
||
|
[System.Diagnostics.DebuggerNonUserCode]
|
||
|
internal class CacheNeedle<T> : WeakNeedle<T>, ICacheNeedle<T>, IEquatable<CacheNeedle<T>>, IWaitablePromise<T>
|
||
|
where T : class
|
||
|
{
|
||
|
[NonSerialized]
|
||
|
private Thread _initializerThread;
|
||
|
|
||
|
private Func<T> _valueFactory;
|
||
|
|
||
|
// Can be null
|
||
|
private StructNeedle<ManualResetEventSlim> _waitHandle;
|
||
|
|
||
|
public CacheNeedle(Func<T> valueFactory)
|
||
|
: base(default(T))
|
||
|
{
|
||
|
if (valueFactory == null)
|
||
|
{
|
||
|
throw new ArgumentNullException("valueFactory");
|
||
|
}
|
||
|
_valueFactory = valueFactory;
|
||
|
_waitHandle = new StructNeedle<ManualResetEventSlim>(new ManualResetEventSlim(false));
|
||
|
}
|
||
|
|
||
|
public CacheNeedle(Func<T> valueFactory, T target)
|
||
|
: base(target)
|
||
|
{
|
||
|
if (valueFactory == null)
|
||
|
{
|
||
|
throw new ArgumentNullException("valueFactory");
|
||
|
}
|
||
|
_valueFactory = valueFactory;
|
||
|
_waitHandle = new StructNeedle<ManualResetEventSlim>(new ManualResetEventSlim(false));
|
||
|
}
|
||
|
|
||
|
public CacheNeedle(Func<T> valueFactory, T target, bool cacheExceptions)
|
||
|
: base(target)
|
||
|
{
|
||
|
if (valueFactory == null)
|
||
|
{
|
||
|
throw new ArgumentNullException("valueFactory");
|
||
|
}
|
||
|
_valueFactory = valueFactory;
|
||
|
if (cacheExceptions)
|
||
|
{
|
||
|
_valueFactory = () =>
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
return valueFactory.Invoke();
|
||
|
}
|
||
|
catch (Exception exc)
|
||
|
{
|
||
|
_valueFactory = FuncHelper.GetThrowFunc<T>(exc);
|
||
|
throw;
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
_waitHandle = new StructNeedle<ManualResetEventSlim>(new ManualResetEventSlim(false));
|
||
|
}
|
||
|
|
||
|
public CacheNeedle(T target)
|
||
|
: base(target)
|
||
|
{
|
||
|
_valueFactory = null;
|
||
|
_waitHandle = new StructNeedle<ManualResetEventSlim>(null);
|
||
|
}
|
||
|
|
||
|
public CacheNeedle()
|
||
|
: base(default(T))
|
||
|
{
|
||
|
_valueFactory = null;
|
||
|
_waitHandle = new StructNeedle<ManualResetEventSlim>(null);
|
||
|
}
|
||
|
|
||
|
public T CachedTarget
|
||
|
{
|
||
|
get { return base.Value; }
|
||
|
}
|
||
|
|
||
|
Exception IPromise.Exception
|
||
|
{
|
||
|
get { return Exception; }
|
||
|
}
|
||
|
|
||
|
bool IPromise.IsCanceled
|
||
|
{
|
||
|
get { return false; }
|
||
|
}
|
||
|
|
||
|
public bool IsCompleted
|
||
|
{
|
||
|
get { return !_waitHandle.IsAlive; }
|
||
|
}
|
||
|
|
||
|
bool IPromise.IsFaulted
|
||
|
{
|
||
|
get { return IsFaulted; }
|
||
|
}
|
||
|
|
||
|
public override T Value
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
Initialize();
|
||
|
return base.Value;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
SetTargetValue(value);
|
||
|
ReleaseWaitHandle();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected INeedle<ManualResetEventSlim> WaitHandle
|
||
|
{
|
||
|
get { return _waitHandle; }
|
||
|
}
|
||
|
|
||
|
public bool Equals(CacheNeedle<T> other)
|
||
|
{
|
||
|
return !ReferenceEquals(other, null) && base.Equals(other);
|
||
|
}
|
||
|
|
||
|
public virtual void Initialize()
|
||
|
{
|
||
|
InitializeExtracted();
|
||
|
}
|
||
|
|
||
|
public void ReleaseValueFactory()
|
||
|
{
|
||
|
Volatile.Write(ref _valueFactory, null);
|
||
|
}
|
||
|
|
||
|
public override bool TryGetValue(out T value)
|
||
|
{
|
||
|
value = default(T);
|
||
|
return IsCompleted && base.TryGetValue(out value);
|
||
|
}
|
||
|
|
||
|
public void Wait()
|
||
|
{
|
||
|
if (_initializerThread == Thread.CurrentThread)
|
||
|
{
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
var waitHandle = _waitHandle.Value;
|
||
|
if (waitHandle != null)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
waitHandle.Wait();
|
||
|
}
|
||
|
catch (ObjectDisposedException exception)
|
||
|
{
|
||
|
// Came late to the party, initialization was done
|
||
|
GC.KeepAlive(exception);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[System.Diagnostics.DebuggerNonUserCode]
|
||
|
protected override void Dispose(bool disposeManagedResources)
|
||
|
{
|
||
|
if (TakeDisposalExecution())
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (disposeManagedResources)
|
||
|
{
|
||
|
//Empty
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
ReleaseWaitHandle();
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
_valueFactory = null;
|
||
|
}
|
||
|
base.Dispose(disposeManagedResources);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void Initialize(Action beforeInitialize)
|
||
|
{
|
||
|
if (beforeInitialize == null)
|
||
|
{
|
||
|
throw new ArgumentNullException("beforeInitialize");
|
||
|
}
|
||
|
if (Volatile.Read(ref _valueFactory) == null)
|
||
|
{
|
||
|
// If unable to initialize do nothing
|
||
|
// This happens if
|
||
|
// - initialization is done
|
||
|
// - ReleaseValueFactory was called
|
||
|
// Even if ReleaseValueFactory was called before initialization,
|
||
|
// _target can still be set by SetTargetValue or the Value property
|
||
|
return;
|
||
|
}
|
||
|
try
|
||
|
{
|
||
|
beforeInitialize.Invoke();
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
InitializeExtracted();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void InitializeExtracted()
|
||
|
{
|
||
|
back:
|
||
|
var valueFactory = Interlocked.Exchange(ref _valueFactory, null);
|
||
|
if (valueFactory == null)
|
||
|
{
|
||
|
// Many threads may enter here
|
||
|
// Prevent reentry
|
||
|
if (_initializerThread == Thread.CurrentThread)
|
||
|
{
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
var waitHandle = _waitHandle.Value;
|
||
|
// While _waitHandle.Value is not null it means that we have to wait initialization to complete
|
||
|
if (waitHandle != null)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
// Another thread may have called ReleaseWaitHandle just before the next instruction
|
||
|
waitHandle.Wait();
|
||
|
// Finished waiting...
|
||
|
if (Volatile.Read(ref _valueFactory) != null)
|
||
|
{
|
||
|
// There was an error in the initialization, go back
|
||
|
goto back;
|
||
|
}
|
||
|
ReleaseWaitHandle();
|
||
|
}
|
||
|
catch (ObjectDisposedException exception)
|
||
|
{
|
||
|
// Came late to the party, initialization is done
|
||
|
GC.KeepAlive(exception);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Only one thread enters here
|
||
|
_initializerThread = Thread.CurrentThread;
|
||
|
try
|
||
|
{
|
||
|
// Initialize from the value factory
|
||
|
SetTargetValue(valueFactory.Invoke());
|
||
|
// Initialization done, let any wating thread go
|
||
|
ReleaseWaitHandle();
|
||
|
}
|
||
|
catch (Exception exception)
|
||
|
{
|
||
|
// There was an error during initialization
|
||
|
// Set error state
|
||
|
SetTargetError(exception);
|
||
|
// Restore the valueFactory
|
||
|
Interlocked.CompareExchange(ref _valueFactory, valueFactory, null);
|
||
|
// Let any waiting threads go, but don't get rid of the wait handle
|
||
|
_waitHandle.Value.Set();
|
||
|
throw;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
_initializerThread = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void ReleaseWaitHandle()
|
||
|
{
|
||
|
var waitHandle = _waitHandle.Value;
|
||
|
if (!ReferenceEquals(waitHandle, null))
|
||
|
{
|
||
|
// If another thread is currently waiting, awake it
|
||
|
waitHandle.Set();
|
||
|
// If another thread is about to wait
|
||
|
// Or if another thread started waiting just after the last instruction
|
||
|
// let it throw
|
||
|
waitHandle.Dispose();
|
||
|
}
|
||
|
_waitHandle.Value = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|