using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Chernobyl.ComponentModel
{
///
/// An that can cancel multiple
/// instances and report the progress of those instances
/// as a whole. Each contained by this
/// maintains an equal portion of the progress
/// reported by this . For example, if there are four
/// instances contained within this
/// this would report a
/// progress of 25% when one fully completes, 50% when
/// two fully complete, and so on. Invoking on
/// this would cancel all of the
/// instances contained in this . This type is
/// an implementation of the composite pattern
/// (http://en.wikipedia.org/wiki/Composite_pattern). Note that
/// you can convert an of
/// instances using the extension method
/// .
///
public class TaskEnumerable : ITask, IEnumerable
{
///
/// Initializes a new instance of the class.
///
/// The instances that will be
/// represented as a whole by this .
public TaskEnumerable(IEnumerable tasks)
{
_tasks = tasks;
foreach (ITask task in _tasks)
{
task.ProgressChanged += TaskProgressChanged;
task.Canceled += TaskCanceled;
++_countCache;
}
}
///
/// Invokes on all of the
/// instances contained within this but only
/// if this has not been canceled already and the
/// has not reached 100%. If
/// cancellation is successful, the event will be
/// raised. Note that, when the work being done is on another thread, it
/// is possible that the work will finish just as this method is invoked.
///
public void Cancel()
{
if (IsCanceled == false && ProgressPercentage != 100)
{
IsCanceled = true;
foreach (ITask task in _tasks)
task.Cancel();
// Note: we do not need to raise the Canceled event here
// because this class is already listening for all contained
// ITasks to cancel. If they do, the this class will see it and
// raise the Canceled event automatically. See the
// TaskCanceled(object, EventArgs) method for more information.
}
}
///
/// True if the task has been canceled, false if otherwise.
///
public bool IsCanceled
{
get { return _isCanceled; }
private set
{
bool previousValue = _isCanceled;
_isCanceled = value;
if (previousValue == false && _isCanceled == true && Canceled != null)
Canceled(this, EventArgs.Empty);
}
}
///
/// The percentage of work, from 0% to 100%, that has been completed on
/// this task.
///
public int ProgressPercentage
{
get
{
if (_progressPercentageCache.HasValue == false)
_progressPercentageCache = (int)_tasks.Average(task => task.ProgressPercentage);
return _progressPercentageCache.Value;
}
}
///
/// An event that is raised when the is invoked
/// on this instance. Note that this event is only raised once,
/// regardless of how many times is invoked. This
/// event will also be raised if all of the
/// instances contained in this are canceled.
/// This event will not be invoked if the
/// is at 100%.
///
public event EventHandler Canceled;
///
/// An event that is raised whenever the progress of any
/// instance contained in this has
/// changed. Note that the
/// in the instance included with
/// the event is the of
/// the contained in this
/// that reported its progress and caused this event to be raised.
///
public event ProgressChangedEventHandler ProgressChanged;
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through
/// the collection.
///
public IEnumerator GetEnumerator()
{
return _tasks.GetEnumerator();
}
///
/// Returns an enumerator that iterates through a collection.
///
///
/// An object that can be used to iterate
/// through the collection.
///
IEnumerator IEnumerable.GetEnumerator()
{
return _tasks.GetEnumerator();
}
///
/// An event handler that is assigned to the
/// event of the
/// contained in this . This method is
/// responsible calculating the overall
/// and raising of the event.
///
/// The instance that generated the event.
/// The instance
/// containing the event data.
void TaskProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Force the ProgressPercentage to recalculate itself.
_progressPercentageCache = null;
if(ProgressChanged != null)
ProgressChanged(this, new ProgressChangedEventArgs(ProgressPercentage, e.UserState));
}
///
/// An event handler that is assigned to the
/// event of the
/// contained in this . This method is
/// responsible for checking if all the instances
/// contained within this have been
/// canceled and raising the event if so.
///
/// The instance that generated the event.
/// The instance containing the
/// event data.
void TaskCanceled(object sender, EventArgs e)
{
++_canceledTasksCount;
if (_canceledTasksCount == _countCache)
IsCanceled = true;
}
///
/// The instances that will be represented as a
/// whole by this .
///
IEnumerable _tasks;
///
/// The number of instances in this
/// . This value is cached so that we don't
/// have to iterate over the
/// to view the value.
///
int _countCache;
///
/// The number of instances contained within this
/// that have been canceled. This variable
/// is used to determine if the event
/// should raised.
///
int _canceledTasksCount;
///
/// The backing field to .
///
bool _isCanceled;
///
/// The backing field and cache to . In
/// order to calculate the we have to
/// iterate over the to get the average. We cache
/// the value to prevent us from having to do it more than we have to.
///
int? _progressPercentageCache;
}
///
/// Extensions methods used with .
///
public static class TaskEnumerableExtensions
{
///
/// Takes a group of instances
/// and converts them to a single that represents
/// the entire collection of instances. This method
/// uses to accomplish this.
///
/// The instances that are to be
/// represented as one .
/// That that represents the whole group of
/// instances.
public static ITask AsComposite(this IEnumerable tasks)
{
return new TaskEnumerable(tasks);
}
}
}