Background
I retrieve data from a 3rd party API, over HTTP. It's often slow, and sometimes just fails. I needed a class that would keep getting data from that source, and keep it in memory.
So, this "TaskKeeper" class's purpose is:
- Have a timer (interval lengths are parameters of the class)
- Every timer-tick, perform a "getter" function, on a separate thread, as a "task" (don't hang the current thread).
- save the data as a property. If the data was received, the old data is discarded, if not, keep the old data.
The Question
I'm asking, more specifically, if I can improve the code which handles the "Task" object, but I would appreciate any helpful comments, about any of the code!
The Code
public class TaskKeeper<TParam, TResult> : IDisposable
where TParam : class
{
// ReSharper disable StaticFieldInGenericType
//By design: Different Locker object, per type of task keeper.
private static readonly object Locker = new object();
private static readonly object Locker2 = new object();
// ReSharper restore StaticFieldInGenericType
private readonly Func<ITaskKeeperFuncParam<TParam>, ITaskKeeperFuncResult<TResult>> _dataGetter;
private readonly TParam _parameter;
private CancellationTokenSource _cancellationTokenSource;
private Task<ITaskKeeperFuncResult<TResult>> _task;
private Timer _timer;
public TaskKeeper(Func<ITaskKeeperFuncResult<TResult>> dataGetter, int timerInterval, int dueTime,
TParam parameter)
{
_parameter = parameter;
Func<ITaskKeeperFuncParam<TParam>, ITaskKeeperFuncResult<TResult>> noParamFunc = arg => dataGetter();
_dataGetter = noParamFunc;
SetTimer(timerInterval, dueTime);
}
public TaskKeeper(Func<ITaskKeeperFuncParam<TParam>, ITaskKeeperFuncResult<TResult>> dataGetter,
int timerInterval, int dueTime, TParam parameter)
{
_dataGetter = dataGetter;
_parameter = parameter;
SetTimer(timerInterval, dueTime);
}
public TResult Data { get; private set; }
public DateTime LastSuccess { get; private set; }
#region IDisposable Members
public void Dispose()
{
//stop timer
_timer.Change(Timeout.Infinite, Timeout.Infinite);
_timer.Dispose();
//cancel task
_cancellationTokenSource.Cancel();
}
#endregion
public event EventHandler<EventArgs<TResult>> DataChangedHandler;
public void OnDataChangedHandler(EventArgs<TResult> e)
{
//from C# 4.0 in a nutshell:
////In multithreaded scenarios (Chapter 21), you need to assign the
////delegate to a temporary variable before testing and invoking it
////in order to be thread-safe:
////var temp = PriceChanged;
////if (temp != null) temp (this, e);
EventHandler<EventArgs<TResult>> handler = DataChangedHandler;
if (handler != null) handler(this, e);
}
private void SetTimer(int timerInterval, int dueTime)
{
_timer = new Timer(CheckForNewData, _parameter, dueTime, timerInterval);
}
/// <summary>
/// If the previous task has finished, executes it anew.
/// </summary>
public void CheckForNewData(object param)
{
lock (Locker)
{
if (_task != null && !_task.IsCompleted && !_task.IsCanceled) return;
var closure = new Func<ITaskKeeperFuncResult<TResult>>(() => Wrapper(param));
_cancellationTokenSource = new CancellationTokenSource();
_task = Task<ITaskKeeperFuncResult<TResult>>.Factory.StartNew(closure, _cancellationTokenSource.Token);
_task.ContinueWith(RunWhenFuncFinished);
}
}
private ITaskKeeperFuncResult<TResult> Wrapper(object paramObject)
{
var taskKeeperFuncParam = new TaskKeeperFuncParam<TParam>(LastSuccess, (TParam)paramObject);
return _dataGetter(taskKeeperFuncParam);
}
private void RunWhenFuncFinished(Task<ITaskKeeperFuncResult<TResult>> task)
{
if (!task.IsFaulted)
{
ITaskKeeperFuncResult<TResult> result = task.Result;
if (!result.Success) return;
lock (Locker2)
{
Data = result.Result;
LastSuccess = DateTime.Now;
}
EventArgs<TResult> args = DataChangedHandler.CreateArgs(Data);
OnDataChangedHandler(args);
}
else
{
Logger.Current.LogError(task.Exception);
}
}
}
EDIT: @almaz asked "Why do you submit last success date to the method that is supposed just to query the data via HTTP?" - The answer is, that it's one of the parameters needed for the HTTP request. It tells the 3rd party repository to return all data "since" the last time we succeeded in retrieving the information.