Because I needed to execute some actions one by one in seperate thread (not to block GUI) and I couldn't use Task.ContinueWith
from .NET 4.0 I decided to write it by myself.
Here is how it evolved thanks to your suggestions.
public class ActionQueue
{
private Thread _thread;
private bool _isProcessed = false;
private object _queueSync = new object();
private readonly Queue<Action> _actions = new Queue<Action>();
private SynchronizationContext _context;
/// <summary>
/// Occurs when one of executed action throws unhandled exception.
/// </summary>
public event CrossThreadExceptionEventHandler ExceptionOccured;
/// <summary>
/// Occurs when all actions in queue are finished.
/// </summary>
public event EventHandler ProcessingFinished;
/// <summary>
/// Gets enqueued actions.
/// </summary>
public IEnumerable<Action> Actions
{
get
{
lock (_queueSync)
{
return new ReadOnlyCollection<Action>(_actions.ToList());
}
}
}
protected virtual void Execute()
{
_isProcessed = true;
try
{
while (true)
{
Action action = null;
lock (_queueSync)
{
if (_actions.Count == 0)
break;
else
action = _actions.Dequeue();
}
action.Invoke();
}
if (ProcessingFinished != null)
{
_context.Send(s => ProcessingFinished(this, EventArgs.Empty), null);
}
}
catch (ThreadAbortException)
{
// Execution aborted
}
catch (Exception ex)
{
if (ExceptionOccured != null)
{
_context.Send(s => ExceptionOccured(this, new CrossThreadExceptionEventArgs(ex)), null);
}
}
finally
{
_isProcessed = false;
}
}
/// <summary>
/// Starts processing current queue.
/// </summary>
/// <returns>Returns true if execution was started.</returns>
public virtual bool Process()
{
if (!_isProcessed)
{
_context = SynchronizationContext.Current;
_thread = new Thread(Execute);
_thread.Start();
return true;
}
else
{
return false;
}
}
/// <summary>
/// Enqueues action to process.
/// </summary>
/// <param name="action">Action to enqueue.</param>
public void Enqueue(Action action)
{
lock (_queueSync)
{
_actions.Enqueue(action);
}
}
/// <summary>
/// Clears queue.
/// </summary>
public void Clear()
{
lock (_queueSync)
{
_actions.Clear();
}
}
/// <summary>
/// Aborts execution of current queue.
/// </summary>
/// <returns>Returns true if execution was aborted.</returns>
public bool Abort()
{
if (_isProcessed)
{
_thread.Abort();
return true;
}
else
{
return false;
}
}
}
public delegate void CrossThreadExceptionEventHandler(object sender, CrossThreadExceptionEventArgs e);
public class CrossThreadExceptionEventArgs : EventArgs
{
public CrossThreadExceptionEventArgs(Exception exception)
{
this.Exception = exception;
}
public Exception Exception
{
get;
set;
}
}
Here is some usage example:
ActionQueue queue = new ActionQueue();
queue.Enqueue(SomeMethod);
queue.Process();
foreach(var item in collection)
{
var itemToProcess = item;
queue.Enqueue(() => SomeMethod(itemToProcess));
}
queue.Process();
Edits:
- Implemented observable pattern
- Synchronization context added
IsProcessed
property removed