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
5 years ago
|
// 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
|