Beeing on .NET 3.5 i don't have access to the TPL. Yet i have become fed up of having to manage manually the logic behind delegate.BeginInvoke type of scenarios each time and i set up to implement my own Task class.
Functionality it should support:
- starting a parallel execution (using a thread pool thread)
- capable of Waiting on the result with a specific timeout
- capable of aborting said execution
- should provide event for callbacks to attach to
This is my implementation. Can you guys please review it and specify if there are any issues i might not have considered or maybe more effective ways to implement some things?
public class Task<T>
{
#region Members
private IAsyncResult _async;
private Func<T> _action;
private Func<T> _innerAction;
private WaitHandle _waitHandle;
private Thread _thread;
private object _completedLock = new object();
private object _abortLock = new object();
private T _result;
private bool _endCalled = false;
#endregion
#region Properties
public object Tag { get; private set; }
public bool IsCompleted { get; private set; }
public bool IsRunning { get; private set; }
public T Result
{
get
{
if (!_endCalled)
{
lock (_completedLock)
{
if (!_endCalled)
{
try
{
if (_async != null)
{
_result = _innerAction.EndInvoke(_async);
IsCompleted = true;
}
}
finally
{
_endCalled = true;
}
}
}
}
return _result;
}
}
#endregion
#region Events
public event EventHandler Completed;
#endregion
public Task(Func<T> action, object tag)
: this(action)
{
Tag = tag;
}
public Task(Func<T> action)
{
_action = action;
_innerAction = () =>
{
try
{
_thread = Thread.CurrentThread;
var result = _action();
return result;
}
finally
{
lock (_abortLock)
{
IsRunning = false;
}
}
};
}
#region Public Methods
public void Run()
{
if (!IsRunning || IsCompleted)
{
lock (_completedLock)
{
if (!IsRunning || IsCompleted)
{
ResetState();
_async = _innerAction.BeginInvoke(obj => OnCompleted(), null);
IsRunning = true;
return;
}
}
}
throw new InvalidOperationException("Task is already running");
}
public bool WaitForCompletion(TimeSpan timeout)
{
if (IsRunning && !IsCompleted)
{
lock (_completedLock)
{
if (!IsCompleted)
{
_waitHandle = _async.AsyncWaitHandle;
return _waitHandle.WaitOne(timeout);
}
}
}
return IsCompleted;
}
public bool WaitForCompletion(int timeoutMilliseconds)
{
return WaitForCompletion(TimeSpan.FromMilliseconds(timeoutMilliseconds));
}
public bool Abort()
{
bool result = false;
if (!IsCompleted)
{
lock (_abortLock)
{
if (!IsCompleted && IsRunning)
{
if (_thread != null)
{
_thread.Abort();
}
result = true;
}
ResetState();
}
}
return result;
}
#endregion
private void ResetState()
{
_async = null;
_endCalled = false;
_result = default(T);
_thread = null;
IsCompleted = false;
IsRunning = false;
}
private void OnCompleted()
{
lock (_completedLock)
{
IsCompleted = true;
if (_waitHandle != null)
{
_waitHandle.Close();
_waitHandle = null;
}
}
if (Completed != null)
{
try
{
Completed(this, EventArgs.Empty);
}
catch
{
//We swallow this as there is no way to catch it at an upper level
//on the execution thread and do something about it.
//Callbacks should not throw anyway.
}
}
}
}