#region CPL License /* Nuclex Framework Copyright (C) 2002-2008 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using Nuclex.Support.Tracking; namespace Nuclex.Support.Scheduling { /// Operation that sequentially executes a series of operations /// /// Type of the child operations the QueueOperation will contain /// public class OperationQueue : Operation, IProgressReporter where OperationType : Operation { /// will be triggered to report when progress has been achieved public event EventHandler AsyncProgressChanged; /// Initializes a new queue operation with default weights /// Child operations to execute in this operation /// /// All child operations will have a default weight of 1.0 /// public OperationQueue(IEnumerable childs) : this() { // Construct a WeightedTransaction with the default weight for each // transaction and wrap it in an ObservedTransaction foreach(OperationType operation in childs) this.children.Add(new WeightedTransaction(operation)); // Since all transactions have a weight of 1.0, the total weight is // equal to the number of transactions in our list this.totalWeight = (float)this.children.Count; } /// Initializes a new queue operation with custom weights /// Child operations to execute in this operation public OperationQueue(IEnumerable> childs) : this() { // Construct an ObservedTransactionn around each of the WeightedTransactions foreach(WeightedTransaction operation in childs) { this.children.Add(operation); // Sum up the total weight this.totalWeight += operation.Weight; } } /// Initializes a new queue operation private OperationQueue() { this.asyncOperationEndedDelegate = new EventHandler(asyncOperationEnded); this.asyncOperationProgressChangedDelegate = new EventHandler( asyncOperationProgressChanged ); this.children = new List>(); } /// Provides access to the child operations of this queue public IList> Children { get { return this.children; } } /// Launches the background operation public override void Start() { startCurrentOperation(); } /// /// Allows the specific request implementation to re-throw an exception if /// the background process finished unsuccessfully /// protected override void ReraiseExceptions() { if(this.exception != null) throw this.exception; } /// Fires the progress update event /// Progress to report (ranging from 0.0 to 1.0) /// /// Informs the observers of this transaction about the achieved progress. /// protected virtual void OnAsyncProgressChanged(float progress) { OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); } /// Fires the progress update event /// Progress to report (ranging from 0.0 to 1.0) /// /// Informs the observers of this transaction about the achieved progress. /// Allows for classes derived from the transaction class to easily provide /// a custom event arguments class that has been derived from the /// transaction's ProgressUpdateEventArgs class. /// protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { EventHandler copy = AsyncProgressChanged; if(copy != null) copy(this, eventArguments); } /// Prepares the current operation and calls its Begin() method /// /// This subscribes the queue to the events of to the current operation /// and launches the operation by calling its Begin() method. /// private void startCurrentOperation() { OperationType operation = this.children[this.currentOperationIndex].Transaction; operation.AsyncEnded += this.asyncOperationEndedDelegate; IProgressReporter progressReporter = operation as IProgressReporter; if(progressReporter != null) progressReporter.AsyncProgressChanged += this.asyncOperationProgressChangedDelegate; operation.Start(); } /// Disconnects from the current operation and calls its End() method /// /// This unsubscribes the queue from the current operation's events, calls End() /// on the operation and, if the operation didn't have an exception to report, /// counts up the accumulated progress of the queue. /// private void endCurrentOperation() { OperationType operation = this.children[this.currentOperationIndex].Transaction; // Disconnect from the operation's events operation.AsyncEnded -= this.asyncOperationEndedDelegate; IProgressReporter progressReporter = operation as IProgressReporter; if(progressReporter != null) progressReporter.AsyncProgressChanged -= this.asyncOperationProgressChangedDelegate; try { operation.Join(); // Add the operations weight to the total amount of completed weight in the queue this.completedWeight += this.children[this.currentOperationIndex].Weight; // Trigger another progress update OnAsyncProgressChanged(this.completedWeight / this.totalWeight); } catch(Exception exception) { this.exception = exception; } } /// Called when the current executing operation ends /// Operation that ended /// Not used private void asyncOperationEnded(object sender, EventArgs e) { // Unsubscribe from the current operation's events and update the // accumulating progress counter endCurrentOperation(); // Only jump to the next operation if no exception occured if(this.exception == null) { ++this.currentOperationIndex; // Execute the next operation unless we reached the end of the queue if(this.currentOperationIndex < this.children.Count) { startCurrentOperation(); return; } } // Either an exception has occured or we reached the end of the operation // queue. In any case, we need to report that the operation is over. OnAsyncEnded(); } /// Called when currently executing operation makes progress /// Operation that has achieved progress /// Not used private void asyncOperationProgressChanged(object sender, ProgressReportEventArgs e) { // Determine the completed weight of the currently executing operation float currentOperationCompletedWeight = e.Progress * this.children[this.currentOperationIndex].Weight; // Build the total normalized amount of progress for the queue float progress = (this.completedWeight + currentOperationCompletedWeight) / this.totalWeight; // Done, we can send the actual progress to any event subscribers OnAsyncProgressChanged(progress); } /// Delegate to the asyncOperationEnded() method private EventHandler asyncOperationEndedDelegate; /// Delegate to the asyncOperationProgressUpdated() method private EventHandler asyncOperationProgressChangedDelegate; /// Operations being managed in the queue private List> children; /// Summed weight of all operations in the queue private float totalWeight; /// Accumulated weight of the operations already completed private float completedWeight; /// Index of the operation currently executing private int currentOperationIndex; /// Exception that has occured in the background process private volatile Exception exception; } } // namespace Nuclex.Support.Scheduling