#region CPL License /* Nuclex Framework Copyright (C) 2002-2009 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 System.Collections.ObjectModel; using System.Threading; using Nuclex.Support.Collections; namespace Nuclex.Support.Tracking { /// Forms a single transaction from a group of transactions /// Type of transactions to manage as a set public class TransactionGroup : Transaction, IDisposable, IProgressReporter where TransactionType : Transaction { /// will be triggered to report when progress has been achieved public event EventHandler AsyncProgressChanged; /// Initializes a new transaction group /// Transactions to track with this group /// /// Uses a default weighting factor of 1.0 for all transactions. /// public TransactionGroup(IEnumerable children) { List> childrenList = new List>(); // Construct a WeightedTransaction with the default weight for each // transaction and wrap it in an ObservedTransaction foreach(TransactionType transaction in children) { childrenList.Add( new ObservedWeightedTransaction( new WeightedTransaction(transaction), new ObservedWeightedTransaction.ReportDelegate( asyncProgressUpdated ), new ObservedWeightedTransaction.ReportDelegate( asyncChildEnded ) ) ); } // 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)childrenList.Count; // Thread.MemoryBarrier(); // not needed because children is volatile this.children = childrenList; // Any asyncEnded events being receiving from the transactions until now // would have been ignored, so we need to check again here asyncChildEnded(); } /// Initializes a new transaction group /// Transactions to track with this group public TransactionGroup( IEnumerable> children ) { List> childrenList = new List>(); // Construct an ObservedTransaction around each of the WeightedTransactions foreach(WeightedTransaction transaction in children) { childrenList.Add( new ObservedWeightedTransaction( transaction, new ObservedWeightedTransaction.ReportDelegate( asyncProgressUpdated ), new ObservedWeightedTransaction.ReportDelegate( asyncChildEnded ) ) ); // Sum up the total weight this.totalWeight += transaction.Weight; } this.children = childrenList; // Any asyncEnded events being receiving from the transactions until now // would have been ignored, so we need to check again here asyncChildEnded(); } /// Immediately releases all resources owned by the object public void Dispose() { if(this.children != null) { // Dispose all the observed transactions, disconnecting the events from the // actual transactions so the GC can more easily collect this class for(int index = 0; index < this.children.Count; ++index) this.children[index].Dispose(); this.children = null; this.wrapper = null; } } /// Childs contained in the transaction set public IList> Children { get { // The wrapper is constructed only when needed. Most of the time, users will // just create a transaction group and monitor its progress without ever using // the Childs collection. if(this.wrapper == null) { // This doesn't need a lock because it's a stateless wrapper. // If it is constructed twice, then so be it, no problem at all. this.wrapper = new WeightedTransactionWrapperCollection( this.children ); } return this.wrapper; } } /// Fires the progress update event /// Progress to report (ranging from 0.0 to 1.0) /// /// Informs the observers of this transactions 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); } /// /// Called when the progress of one of the observed transactions changes /// private void asyncProgressUpdated() { if(this.children == null) { return; } float totalProgress = 0.0f; // Calculate the sum of the progress reported by our child transactions, // scaled to the weight each transaction has assigned to it. for(int index = 0; index < this.children.Count; ++index) { totalProgress += this.children[index].Progress * this.children[index].WeightedTransaction.Weight; } // Calculate the actual combined progress if(this.totalWeight > 0.0f) totalProgress /= this.totalWeight; // Send out the progress update OnAsyncProgressChanged(totalProgress); } /// /// Called when an observed transaction ends /// private void asyncChildEnded() { // If a transaction reports its end durign the constructor, it will end up here // where the collection has not been assigned yet, allowing us to skip the // check until all transactions are there (otherwise, we might invoke // OnAsyncended() early, because all transactions in the list seem to have ended // despite the fact that the constructor hasn't finished adding transactions yet) if(this.children == null) { return; } // If there's still at least one transaction going, don't report that // the transaction group has finished yet. for(int index = 0; index < this.children.Count; ++index) if(!this.children[index].WeightedTransaction.Transaction.Ended) return; // All child transactions have ended, so the set has now ended as well if(Interlocked.Exchange(ref this.endedCalled, 1) == 0) { OnAsyncEnded(); } } /// Transactions being managed in the set private volatile List> children; /// /// Wrapper collection for exposing the child transactions under the /// WeightedTransaction interface /// private volatile WeightedTransactionWrapperCollection wrapper; /// Summed weight of all transactions in the set private float totalWeight; /// Whether we already called OnAsyncEnded private int endedCalled; } } // namespace Nuclex.Support.Tracking