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.
419 lines
13 KiB
419 lines
13 KiB
5 years ago
|
#if NET20 || NET30 || NET35 || !NET_4_6
|
||
|
|
||
|
using LinqInternal.Threading;
|
||
|
|
||
|
namespace System.Threading
|
||
|
{
|
||
|
public class ManualResetEventSlim : IDisposable
|
||
|
{
|
||
|
private const int _defaultSpinCount = 10;
|
||
|
|
||
|
private readonly int _spinCount;
|
||
|
private ManualResetEvent _handle;
|
||
|
|
||
|
// _requested: -1 = Disposed, 0 = Not requested, 1 = Requested, 2 = _handle ready
|
||
|
private int _requested;
|
||
|
|
||
|
// _state: -1 = Disposed, 0 = Not Set, 1 = Set -- Do not set to 0 or 1 when _requested == 1
|
||
|
private int _state;
|
||
|
|
||
|
public ManualResetEventSlim()
|
||
|
: this(false)
|
||
|
{
|
||
|
//Empty
|
||
|
}
|
||
|
|
||
|
public ManualResetEventSlim(bool initialState)
|
||
|
{
|
||
|
_state = initialState ? 1 : 0;
|
||
|
_spinCount = _defaultSpinCount;
|
||
|
}
|
||
|
|
||
|
public ManualResetEventSlim(bool initialState, int spinCount)
|
||
|
{
|
||
|
if (spinCount < 0 || spinCount > 2047)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException("spinCount");
|
||
|
}
|
||
|
_spinCount = spinCount;
|
||
|
_state = initialState ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
public bool IsSet
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
// The value returned by this property should be considered out of sync
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
var handle = GetWaitHandle();
|
||
|
if (handle != null)
|
||
|
{
|
||
|
return handle.WaitOne(0);
|
||
|
}
|
||
|
return Thread.VolatileRead(ref _state) != 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int SpinCount
|
||
|
{
|
||
|
get { return _spinCount; }
|
||
|
}
|
||
|
|
||
|
public WaitHandle WaitHandle
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
return RetriveWaitHandle();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Dispose()
|
||
|
{
|
||
|
Dispose(true);
|
||
|
GC.SuppressFinalize(this);
|
||
|
}
|
||
|
|
||
|
public void Reset()
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
var handle = GetWaitHandle();
|
||
|
if (handle != null)
|
||
|
{
|
||
|
handle.Reset();
|
||
|
}
|
||
|
Thread.VolatileWrite(ref _state, 0);
|
||
|
}
|
||
|
|
||
|
public void Set()
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
// Silent fail
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
var handle = GetWaitHandle();
|
||
|
if (handle != null)
|
||
|
{
|
||
|
handle.Set();
|
||
|
}
|
||
|
if (Interlocked.CompareExchange(ref _state, 1, 0) == 0)
|
||
|
{
|
||
|
handle = GetWaitHandle();
|
||
|
if (handle != null)
|
||
|
{
|
||
|
handle.Set();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (ObjectDisposedException ex)
|
||
|
{
|
||
|
GC.KeepAlive(ex);
|
||
|
// Silent fail
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Wait()
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
var spinWait = new SpinWait();
|
||
|
var spinCount = _spinCount;
|
||
|
if (!IsSet)
|
||
|
{
|
||
|
retry:
|
||
|
if (!IsSet)
|
||
|
{
|
||
|
if (spinCount > 0)
|
||
|
{
|
||
|
spinCount--;
|
||
|
spinWait.SpinOnce();
|
||
|
goto retry;
|
||
|
}
|
||
|
var handle = RetriveWaitHandle();
|
||
|
handle.WaitOne();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool Wait(int millisecondsTimeout)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
if (millisecondsTimeout < -1)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException("millisecondsTimeout");
|
||
|
}
|
||
|
if (millisecondsTimeout == -1)
|
||
|
{
|
||
|
Wait();
|
||
|
return true;
|
||
|
}
|
||
|
return WaitExtracted(millisecondsTimeout);
|
||
|
}
|
||
|
|
||
|
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
if (millisecondsTimeout < -1)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException("millisecondsTimeout");
|
||
|
}
|
||
|
if (millisecondsTimeout == -1)
|
||
|
{
|
||
|
WaitExtracted(cancellationToken);
|
||
|
return true;
|
||
|
}
|
||
|
return WaitExtracted(millisecondsTimeout, cancellationToken);
|
||
|
}
|
||
|
|
||
|
public void Wait(CancellationToken cancellationToken)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
WaitExtracted(cancellationToken);
|
||
|
}
|
||
|
|
||
|
public bool Wait(TimeSpan timeout)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
var milliseconds = timeout.TotalMilliseconds;
|
||
|
return WaitExtracted((int)milliseconds);
|
||
|
}
|
||
|
|
||
|
public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _state) == -1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
var milliseconds = timeout.TotalMilliseconds;
|
||
|
return WaitExtracted((int)milliseconds, cancellationToken);
|
||
|
}
|
||
|
|
||
|
protected virtual void Dispose(bool disposing)
|
||
|
{
|
||
|
if (disposing)
|
||
|
{
|
||
|
if (Interlocked.Exchange(ref _state, -1) != -1)
|
||
|
{
|
||
|
Thread.VolatileWrite(ref _requested, -1);
|
||
|
var handle = Interlocked.Exchange(ref _handle, null);
|
||
|
if (handle != null)
|
||
|
{
|
||
|
handle.Close();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private ManualResetEvent GetWaitHandle()
|
||
|
{
|
||
|
var found = Thread.VolatileRead(ref _requested);
|
||
|
switch (found)
|
||
|
{
|
||
|
case -1:
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
case 0:
|
||
|
return null;
|
||
|
|
||
|
case 1:
|
||
|
// Found 1, another thread is creating the wait handle
|
||
|
ThreadingHelper.SpinWaitUntil(ref _requested, 2);
|
||
|
goto default;
|
||
|
default:
|
||
|
// Found 2, the wait handle is already created
|
||
|
// Check if dispose has been called
|
||
|
return TryGetWaitHandleExtracted();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private ManualResetEvent RetriveWaitHandle()
|
||
|
{
|
||
|
// At the end of this method: _requested will be 2 or ObjectDisposedException is thrown
|
||
|
var found = Interlocked.CompareExchange(ref _requested, 1, 0);
|
||
|
switch (found)
|
||
|
{
|
||
|
case -1:
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
case 0:
|
||
|
// Found 0, was set to 1, create the wait handle
|
||
|
var isSet = Thread.VolatileRead(ref _state) != 0;
|
||
|
// State may have been set here
|
||
|
var created = new ManualResetEvent(isSet);
|
||
|
if (Interlocked.CompareExchange(ref _handle, created, null) != null)
|
||
|
{
|
||
|
created.Close();
|
||
|
}
|
||
|
Thread.VolatileWrite(ref _requested, 2);
|
||
|
goto default;
|
||
|
case 1:
|
||
|
// Found 1, another thread is creating the wait handle
|
||
|
ThreadingHelper.SpinWaitUntil(ref _requested, 2);
|
||
|
goto default;
|
||
|
default:
|
||
|
// Found 2, the wait handle is already created
|
||
|
// Check if dispose has been called
|
||
|
return TryGetWaitHandleExtracted();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private ManualResetEvent TryGetWaitHandleExtracted()
|
||
|
{
|
||
|
var handle = Volatile.Read(ref _handle);
|
||
|
if (handle != null)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _requested) == 2)
|
||
|
{
|
||
|
return handle;
|
||
|
}
|
||
|
handle.Close();
|
||
|
}
|
||
|
Thread.VolatileWrite(ref _requested, -1);
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
|
||
|
private bool WaitExtracted(int millisecondsTimeout)
|
||
|
{
|
||
|
var spinWait = new SpinWait();
|
||
|
var spinCount = _spinCount;
|
||
|
if (IsSet)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
var start = ThreadingHelper.TicksNow();
|
||
|
retry_longTimeout:
|
||
|
if (IsSet)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
var elapsed = ThreadingHelper.Milliseconds(ThreadingHelper.TicksNow() - start);
|
||
|
if (elapsed < millisecondsTimeout)
|
||
|
{
|
||
|
if (spinCount > 0)
|
||
|
{
|
||
|
spinCount--;
|
||
|
spinWait.SpinOnce();
|
||
|
goto retry_longTimeout;
|
||
|
}
|
||
|
var handle = RetriveWaitHandle();
|
||
|
var remaining = millisecondsTimeout - (int)elapsed;
|
||
|
if (remaining > 0)
|
||
|
{
|
||
|
return handle.WaitOne(remaining);
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private bool WaitExtracted(int millisecondsTimeout, CancellationToken cancellationToken)
|
||
|
{
|
||
|
var spinWait = new SpinWait();
|
||
|
var spinCount = _spinCount;
|
||
|
if (IsSet)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
var start = ThreadingHelper.TicksNow();
|
||
|
retry_longTimeout:
|
||
|
if (IsSet)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
var elapsed = ThreadingHelper.Milliseconds(ThreadingHelper.TicksNow() - start);
|
||
|
if (elapsed < millisecondsTimeout)
|
||
|
{
|
||
|
if (spinCount > 0)
|
||
|
{
|
||
|
spinCount--;
|
||
|
spinWait.SpinOnce();
|
||
|
goto retry_longTimeout;
|
||
|
}
|
||
|
var handle = RetriveWaitHandle();
|
||
|
var remaining = millisecondsTimeout - (int)elapsed;
|
||
|
if (remaining > 0)
|
||
|
{
|
||
|
var result = WaitHandle.WaitAny
|
||
|
(
|
||
|
new[]
|
||
|
{
|
||
|
handle,
|
||
|
cancellationToken.WaitHandle
|
||
|
},
|
||
|
remaining
|
||
|
);
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
return result != WaitHandle.WaitTimeout;
|
||
|
}
|
||
|
}
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private void WaitExtracted(CancellationToken cancellationToken)
|
||
|
{
|
||
|
var spinWait = new SpinWait();
|
||
|
var spinCount = _spinCount;
|
||
|
retry:
|
||
|
if (!IsSet)
|
||
|
{
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
if (spinCount > 0)
|
||
|
{
|
||
|
spinCount--;
|
||
|
spinWait.SpinOnce();
|
||
|
goto retry;
|
||
|
}
|
||
|
var handle = RetriveWaitHandle();
|
||
|
WaitHandle.WaitAny
|
||
|
(
|
||
|
new[]
|
||
|
{
|
||
|
handle,
|
||
|
cancellationToken.WaitHandle
|
||
|
}
|
||
|
);
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|