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.
377 lines
11 KiB
377 lines
11 KiB
// Needed for NET35 (TASK) |
|
#if !NET_4_6 |
|
using System; |
|
using System.Threading; |
|
using LinqInternal.Collections.ThreadSafe; |
|
using LinqInternal.Core; |
|
using LinqInternal.Threading.Needles; |
|
|
|
namespace LinqInternal.Threading |
|
{ |
|
internal class Timeout : IPromise |
|
{ |
|
protected Action Callback; |
|
private const int _canceled = 4; |
|
private const int _canceling = 3; |
|
private const int _changing = 6; |
|
private const int _created = 0; |
|
private const int _executed = 2; |
|
private const int _executing = 1; |
|
private static readonly Bucket<Timeout> _root = new Bucket<Timeout>(); |
|
private static int _lastRootIndex = -1; |
|
private readonly int _hashcode; |
|
private int _rootIndex = -1; |
|
private long _startTime; |
|
private int _status; |
|
private long _targetTime; |
|
private Timer _wrapped; |
|
|
|
public Timeout(Action callback, long dueTime) |
|
{ |
|
if (callback == null) |
|
{ |
|
throw new ArgumentNullException("callback"); |
|
} |
|
Callback = callback; |
|
Start(dueTime); |
|
_hashcode = unchecked((int)DateTime.Now.Ticks); |
|
} |
|
|
|
public Timeout(Action callback, long dueTime, CancellationToken token) |
|
{ |
|
if (callback == null) |
|
{ |
|
throw new ArgumentNullException("callback"); |
|
} |
|
if (token.IsCancellationRequested) |
|
{ |
|
Callback = null; |
|
_wrapped = null; |
|
_status = _canceled; |
|
} |
|
else |
|
{ |
|
Callback = callback; |
|
Start(dueTime); |
|
token.Register(Cancel); |
|
} |
|
_hashcode = unchecked((int)DateTime.Now.Ticks); |
|
} |
|
|
|
public Timeout(Action callback, TimeSpan dueTime) |
|
: this(callback, (long)dueTime.TotalMilliseconds) |
|
{ |
|
// Empty |
|
} |
|
|
|
public Timeout(Action callback, TimeSpan dueTime, CancellationToken token) |
|
: this(callback, (long)dueTime.TotalMilliseconds, token) |
|
{ |
|
// Empty |
|
} |
|
|
|
protected Timeout() |
|
{ |
|
_hashcode = unchecked((int)DateTime.Now.Ticks); |
|
} |
|
|
|
~Timeout() |
|
{ |
|
Close(); |
|
} |
|
|
|
Exception IPromise.Exception |
|
{ |
|
get { return null; } |
|
} |
|
|
|
public bool IsCanceled |
|
{ |
|
get { return Volatile.Read(ref _status) == _canceled; } |
|
} |
|
|
|
public bool IsCompleted |
|
{ |
|
get { return Volatile.Read(ref _status) == _executed; } |
|
} |
|
|
|
bool IPromise.IsFaulted |
|
{ |
|
get { return false; } |
|
} |
|
|
|
public static void Launch(Action callback, long dueTime) |
|
{ |
|
if (callback == null) |
|
{ |
|
throw new ArgumentNullException("callback"); |
|
} |
|
var timeout = new Timeout(); |
|
timeout.Callback = () => |
|
{ |
|
try |
|
{ |
|
callback(); |
|
} |
|
finally |
|
{ |
|
UnRoot(timeout); |
|
} |
|
}; |
|
timeout.Start(dueTime); |
|
Root(timeout); |
|
} |
|
|
|
public static void Launch(Action callback, long dueTime, CancellationToken token) |
|
{ |
|
if (callback == null) |
|
{ |
|
throw new ArgumentNullException("callback"); |
|
} |
|
if (token.IsCancellationRequested) |
|
{ |
|
return; |
|
} |
|
var timeout = new Timeout(); |
|
timeout.Callback = () => |
|
{ |
|
try |
|
{ |
|
callback(); |
|
} |
|
finally |
|
{ |
|
UnRoot(timeout); |
|
} |
|
}; |
|
timeout.Start(dueTime); |
|
token.Register(timeout.Cancel); |
|
Root(timeout); |
|
} |
|
|
|
public static void Launch(Action callback, TimeSpan dueTime) |
|
{ |
|
Launch(callback, (long)dueTime.TotalMilliseconds); |
|
} |
|
|
|
public static void Launch(Action callback, TimeSpan dueTime, CancellationToken token) |
|
{ |
|
Launch(callback, (long)dueTime.TotalMilliseconds, token); |
|
} |
|
|
|
public void Cancel() |
|
{ |
|
if (Interlocked.CompareExchange(ref _status, _canceling, _created) == _created) |
|
{ |
|
Close(); |
|
Volatile.Write(ref _status, _canceled); |
|
} |
|
} |
|
|
|
public bool Change(long dueTime) |
|
{ |
|
if (Interlocked.CompareExchange(ref _status, _changing, _created) == _created) |
|
{ |
|
_startTime = ThreadingHelper.Milliseconds(ThreadingHelper.TicksNow()); |
|
_targetTime = _startTime + dueTime; |
|
var wrapped = Interlocked.CompareExchange(ref _wrapped, null, null); |
|
if (wrapped == null) |
|
{ |
|
return false; |
|
} |
|
wrapped.Change(TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(-1)); |
|
Volatile.Write(ref _status, _created); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
public void Change(TimeSpan dueTime) |
|
{ |
|
Change((long)dueTime.TotalMilliseconds); |
|
} |
|
|
|
public long CheckRemaining() |
|
{ |
|
var remaining = _targetTime - ThreadingHelper.Milliseconds(ThreadingHelper.TicksNow()); |
|
if (remaining <= 0) |
|
{ |
|
Finish(null); |
|
return 0; |
|
} |
|
return remaining; |
|
} |
|
|
|
public override bool Equals(object obj) |
|
{ |
|
if (obj is Timeout) |
|
{ |
|
return this == obj; |
|
} |
|
return false; |
|
} |
|
|
|
public override int GetHashCode() |
|
{ |
|
return _hashcode; |
|
} |
|
|
|
protected static void Root(Timeout timeout) |
|
{ |
|
timeout._rootIndex = Interlocked.Increment(ref _lastRootIndex); |
|
_root.Set(timeout._rootIndex, timeout); |
|
} |
|
|
|
protected static void UnRoot(Timeout timeout) |
|
{ |
|
var rootIndex = Interlocked.Exchange(ref timeout._rootIndex, -1); |
|
if (rootIndex != -1) |
|
{ |
|
_root.RemoveAt(rootIndex); |
|
} |
|
} |
|
|
|
protected void Start(long dueTime) |
|
{ |
|
_startTime = ThreadingHelper.Milliseconds(ThreadingHelper.TicksNow()); |
|
_targetTime = _startTime + dueTime; |
|
_wrapped = new Timer(Finish, null, TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(-1)); |
|
} |
|
|
|
private void Close() |
|
{ |
|
var wrapped = Interlocked.Exchange(ref _wrapped, null); |
|
if (wrapped != null) |
|
{ |
|
wrapped.Dispose(); |
|
} |
|
Volatile.Write(ref Callback, null); |
|
GC.SuppressFinalize(this); |
|
} |
|
|
|
private void Finish(object state) |
|
{ |
|
GC.KeepAlive(state); |
|
ThreadingHelper.SpinWaitWhile(ref _status, _changing); |
|
if (Interlocked.CompareExchange(ref _status, _executing, _created) == _created) |
|
{ |
|
var callback = Volatile.Read(ref Callback); |
|
if (callback != null) |
|
{ |
|
callback.Invoke(); |
|
Close(); |
|
Volatile.Write(ref _status, _executed); |
|
} |
|
} |
|
} |
|
} |
|
|
|
internal class Timeout<T> : Timeout |
|
{ |
|
public Timeout(Action<T> callback, long dueTime, T target) |
|
{ |
|
if (callback == null) |
|
{ |
|
throw new ArgumentNullException("callback"); |
|
} |
|
Callback = new ValueActionClosure<T>(callback, target).Invoke; |
|
Start(dueTime); |
|
} |
|
|
|
public Timeout(Action<T> callback, long dueTime, CancellationToken token, T target) |
|
{ |
|
if (callback == null) |
|
{ |
|
throw new ArgumentNullException("callback"); |
|
} |
|
if (token.IsCancellationRequested) |
|
{ |
|
Callback = null; |
|
Cancel(); |
|
} |
|
else |
|
{ |
|
Callback = new ValueActionClosure<T>(callback, target).Invoke; |
|
Start(dueTime); |
|
token.Register(Cancel); |
|
} |
|
} |
|
|
|
public Timeout(Action<T> callback, TimeSpan dueTime, T target) |
|
: this(callback, (long)dueTime.TotalMilliseconds, target) |
|
{ |
|
// Empty |
|
} |
|
|
|
public Timeout(Action<T> callback, TimeSpan dueTime, CancellationToken token, T target) |
|
: this(callback, (long)dueTime.TotalMilliseconds, token, target) |
|
{ |
|
// Empty |
|
} |
|
|
|
private Timeout() |
|
{ |
|
// Empty |
|
} |
|
|
|
public static void Launch(Action<T> callback, long dueTime, T target) |
|
{ |
|
if (callback == null) |
|
{ |
|
throw new ArgumentNullException("callback"); |
|
} |
|
var timeout = new Timeout<T>(); |
|
timeout.Callback = () => |
|
{ |
|
try |
|
{ |
|
callback(target); |
|
} |
|
finally |
|
{ |
|
UnRoot(timeout); |
|
} |
|
}; |
|
timeout.Start(dueTime); |
|
Root(timeout); |
|
} |
|
|
|
public static void Launch(Action<T> callback, long dueTime, CancellationToken token, T target) |
|
{ |
|
if (callback == null) |
|
{ |
|
throw new ArgumentNullException("callback"); |
|
} |
|
if (token.IsCancellationRequested) |
|
{ |
|
return; |
|
} |
|
var timeout = new Timeout<T>(); |
|
timeout.Callback = () => |
|
{ |
|
try |
|
{ |
|
callback(target); |
|
} |
|
finally |
|
{ |
|
UnRoot(timeout); |
|
} |
|
}; |
|
timeout.Start(dueTime); |
|
token.Register(timeout.Cancel); |
|
Root(timeout); |
|
} |
|
|
|
public static void Launch(Action<T> callback, TimeSpan dueTime, T target) |
|
{ |
|
Launch(callback, (long)dueTime.TotalMilliseconds, target); |
|
} |
|
|
|
public static void Launch(Action<T> callback, TimeSpan dueTime, CancellationToken token, T target) |
|
{ |
|
Launch(callback, (long)dueTime.TotalMilliseconds, token, target); |
|
} |
|
} |
|
} |
|
#endif |