|
|
|
|
using System;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using ZeroLevel.Services.Async;
|
|
|
|
|
using ZeroLevel.Services.Logging;
|
|
|
|
|
|
|
|
|
|
namespace ZeroLevel.Services.Shedulling
|
|
|
|
|
{
|
|
|
|
|
public class DateTimeAsyncSheduller
|
|
|
|
|
: IExpirationAsyncSheduller
|
|
|
|
|
{
|
|
|
|
|
private Timer _timer;
|
|
|
|
|
private ExpiredAsyncObject _head = null;
|
|
|
|
|
private AsyncLock _lock = new AsyncLock();
|
|
|
|
|
private volatile bool _stopped = false;
|
|
|
|
|
|
|
|
|
|
#region Ctor
|
|
|
|
|
public DateTimeAsyncSheduller()
|
|
|
|
|
{
|
|
|
|
|
_timer = new Timer(TimerCallbackHandler, null, Timeout.Infinite, Timeout.Infinite);
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
private void TimerCallbackHandler(object state)
|
|
|
|
|
{
|
|
|
|
|
// POP
|
|
|
|
|
ExpiredAsyncObject result = null;
|
|
|
|
|
if (null != _head)
|
|
|
|
|
{
|
|
|
|
|
if (DateTime.Compare(_head.ExpirationDate, DateTime.Now) > 0)
|
|
|
|
|
{
|
|
|
|
|
// Защита на случай если callback был вызван, но до захвата блокировки в нем, она была
|
|
|
|
|
// захвачена другим методом, в этом случае есть риск получить на head дату истечения позже текущего времени.
|
|
|
|
|
|
|
|
|
|
// При изменении времени системы может быть ситуация, при которой в head лежит элемент для которого сработал таймер,
|
|
|
|
|
// но время истечения сместилось, поэтому вызов пересоздания таймера необходим
|
|
|
|
|
ResetTimer();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// SWAP
|
|
|
|
|
result = _head;
|
|
|
|
|
_head = _head.Next;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
if (result != null)
|
|
|
|
|
{
|
|
|
|
|
result.Callback(result.Key).ContinueWith(t =>
|
|
|
|
|
{
|
|
|
|
|
if (t.IsFaulted)
|
|
|
|
|
{
|
|
|
|
|
Exception ex = t.Exception;
|
|
|
|
|
while (ex is AggregateException && ex.InnerException != null)
|
|
|
|
|
ex = ex.InnerException;
|
|
|
|
|
Log.SystemError(ex, "Fault task '{0}' on expiration date '{1:yyyy-MM-dd HH:mm:ss fff}'", result.Key, result.ExpirationDate);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal long Push(ExpiredAsyncObject insert)
|
|
|
|
|
{
|
|
|
|
|
if (insert == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(insert));
|
|
|
|
|
using (_lock.Lock())
|
|
|
|
|
{
|
|
|
|
|
if (null == _head)
|
|
|
|
|
{
|
|
|
|
|
_head = insert;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var cursor = _head;
|
|
|
|
|
var reset = false;
|
|
|
|
|
if (cursor.Key == -1) // if system task for task with big interval (> 2^32 - 2 ms)
|
|
|
|
|
{
|
|
|
|
|
DisableTimer();
|
|
|
|
|
_head = _head.Next; // remove system task from head
|
|
|
|
|
cursor = _head;
|
|
|
|
|
reset = true;
|
|
|
|
|
}
|
|
|
|
|
ExpiredAsyncObject prev = null;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
if (DateTime.Compare(cursor.ExpirationDate, insert.ExpirationDate) > 0)
|
|
|
|
|
{
|
|
|
|
|
insert.Next = cursor;
|
|
|
|
|
if (null == prev) // insert to head
|
|
|
|
|
{
|
|
|
|
|
_head = insert;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
reset = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
prev.Next = insert;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
prev = cursor;
|
|
|
|
|
cursor = cursor.Next;
|
|
|
|
|
if (cursor == null)
|
|
|
|
|
{
|
|
|
|
|
prev.Next = insert;
|
|
|
|
|
}
|
|
|
|
|
} while (cursor != null);
|
|
|
|
|
if (reset)
|
|
|
|
|
{
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_lock.ReleaseLock();
|
|
|
|
|
}
|
|
|
|
|
return insert.Key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Remove(long key)
|
|
|
|
|
{
|
|
|
|
|
using (_lock.Lock())
|
|
|
|
|
{
|
|
|
|
|
if (_head != null)
|
|
|
|
|
{
|
|
|
|
|
ExpiredAsyncObject previous, current;
|
|
|
|
|
FindTaskByKeyWithPreviousTask(key, out previous, out current);
|
|
|
|
|
if (current != null)
|
|
|
|
|
{
|
|
|
|
|
if (_head.Key == current.Key)
|
|
|
|
|
{
|
|
|
|
|
_head = _head.Next;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
previous.Next = current.Next;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_lock.ReleaseLock();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region API
|
|
|
|
|
public long Push(TimeSpan timespan, Func<long, Task> callback)
|
|
|
|
|
{
|
|
|
|
|
return Push(new ExpiredAsyncObject { Callback = callback, ExpirationDate = DateTime.Now.AddMilliseconds(timespan.TotalMilliseconds) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public long Push(DateTime date, Func<long, Task> callback)
|
|
|
|
|
{
|
|
|
|
|
return Push(new ExpiredAsyncObject { Callback = callback, ExpirationDate = date });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DisableTimer()
|
|
|
|
|
{
|
|
|
|
|
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Pause()
|
|
|
|
|
{
|
|
|
|
|
using (_lock.Lock())
|
|
|
|
|
{
|
|
|
|
|
_stopped = true;
|
|
|
|
|
DisableTimer();
|
|
|
|
|
_lock.ReleaseLock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Resume()
|
|
|
|
|
{
|
|
|
|
|
using (_lock.Lock())
|
|
|
|
|
{
|
|
|
|
|
_stopped = false;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
_lock.ReleaseLock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Clean()
|
|
|
|
|
{
|
|
|
|
|
using (_lock.Lock())
|
|
|
|
|
{
|
|
|
|
|
DisableTimer();
|
|
|
|
|
_head = null;
|
|
|
|
|
_lock.ReleaseLock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Control
|
|
|
|
|
private void FindTaskByKeyWithPreviousTask(long key, out ExpiredAsyncObject previous, out ExpiredAsyncObject current)
|
|
|
|
|
{
|
|
|
|
|
if (_head.Key == key)
|
|
|
|
|
{
|
|
|
|
|
previous = null;
|
|
|
|
|
current = _head;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var cursor = _head.Next;
|
|
|
|
|
var prev = _head;
|
|
|
|
|
while (cursor != null)
|
|
|
|
|
{
|
|
|
|
|
if (cursor.Key == key)
|
|
|
|
|
{
|
|
|
|
|
previous = prev;
|
|
|
|
|
current = cursor;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
prev = cursor;
|
|
|
|
|
cursor = cursor.Next;
|
|
|
|
|
}
|
|
|
|
|
previous = null;
|
|
|
|
|
current = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private const uint _max_interval = 4294967294;
|
|
|
|
|
private static readonly TimeSpan _infinite = TimeSpan.FromMilliseconds(Timeout.Infinite);
|
|
|
|
|
|
|
|
|
|
private void ResetTimer()
|
|
|
|
|
{
|
|
|
|
|
if (_timer != null)
|
|
|
|
|
{
|
|
|
|
|
if (null != _head && _stopped == false)
|
|
|
|
|
{
|
|
|
|
|
var diff = (_head.ExpirationDate - DateTime.Now);
|
|
|
|
|
if (diff.TotalMilliseconds > _max_interval)
|
|
|
|
|
{
|
|
|
|
|
var _big_interval_waiting_obj = new ExpiredAsyncObject(true)
|
|
|
|
|
{
|
|
|
|
|
ExpirationDate = DateTime.Now.AddMilliseconds(_max_interval),
|
|
|
|
|
Callback = (key) =>
|
|
|
|
|
{
|
|
|
|
|
using (_lock.Lock())
|
|
|
|
|
{
|
|
|
|
|
ResetTimer();
|
|
|
|
|
_head = null;
|
|
|
|
|
_lock.ReleaseLock();
|
|
|
|
|
}
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
},
|
|
|
|
|
Next = _head
|
|
|
|
|
};
|
|
|
|
|
_head = _big_interval_waiting_obj;
|
|
|
|
|
_timer.Change(_max_interval, Timeout.Infinite);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (diff.Ticks < 0)
|
|
|
|
|
{
|
|
|
|
|
diff = TimeSpan.Zero;
|
|
|
|
|
}
|
|
|
|
|
_timer.Change(diff, _infinite);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
DisableTimer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region IDisposable
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
|
|
|
|
if (_timer != null)
|
|
|
|
|
{
|
|
|
|
|
Clean();
|
|
|
|
|
if (null != _timer)
|
|
|
|
|
{
|
|
|
|
|
_timer.Dispose();
|
|
|
|
|
_timer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|