source: branches/eraser6/pluginsRewrite/Eraser.Plugins/ProgressManager.cs @ 2486

Revision 2486, 21.7 KB checked in by lowjoel, 2 years ago (diff)

Report progress updates by pushing information to the SteppedProgressManager? instance associated with each erasure target. Furthermore, do not manipulate the state of the Task object, instead, let the Task object manage its own Progress state.

Because of this, a new property Tag is created in ProgressManagerBase?, to hold state information.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26
27using Eraser.Util;
28
29namespace Eraser.Plugins
30{
31    /// <summary>
32    /// Manages the progress for any operation.
33    /// </summary>
34    public abstract class ProgressManagerBase
35    {
36        /// <summary>
37        /// Constructor.
38        ///
39        /// This sets the starting time of this task to allow the computation of
40        /// the estimated end time by extrapolating collected data based on the
41        /// amount of time already elapsed.
42        /// </summary>
43        protected ProgressManagerBase()
44        {
45            StartTime = DateTime.Now;
46        }
47
48        /// <summary>
49        /// Resets the starting time of the task. The speed measurement is
50        /// automatically started when the ProgressManagerBase object is created.
51        /// </summary>
52        public void Restart()
53        {
54            StartTime = DateTime.Now;
55            lock (Speeds)
56                Speeds.Reset();
57        }
58
59        /// <summary>
60        /// Gets the percentage of the operation completed.
61        /// </summary>
62        /// <remarks>If the <see cref="ProgressIndeterminate"/> property is true, this
63        /// property will return <see cref="System.Float.NaN"/>.</remarks>
64        public abstract float Progress
65        {
66            get;
67        }
68
69        /// <summary>
70        /// Gets whether the current progress is undefined.
71        /// </summary>
72        public abstract bool ProgressIndeterminate
73        {
74            get;
75        }
76
77        /// <summary>
78        /// Computes the speed of the erase, in percentage of completion per second,
79        /// based on the information collected in the previous 15 seconds.
80        /// </summary>
81        public abstract float Speed
82        {
83            get;
84        }
85
86        /// <summary>
87        /// Calculates the estimated amount of time left based on the total
88        /// amount of information to erase and the current speed of the erase
89        /// </summary>
90        public abstract TimeSpan TimeLeft
91        {
92            get;
93        }
94
95        /// <summary>
96        /// The starting time of this task.
97        /// </summary>
98        public DateTime StartTime
99        {
100            get;
101            private set;
102        }
103
104        /// <summary>
105        /// State information associated with the Progress Manager.
106        /// </summary>
107        public object Tag
108        {
109            get;
110            set;
111        }
112
113        /// <summary>
114        /// Samples the current speed of the task.
115        /// </summary>
116        /// <param name="speed">The speed of the task.</param>
117        protected void SampleSpeed(float speed)
118        {
119            lock (Speeds)
120            {
121                IList<KeyValuePair<DateTime, double>> outliers = Speeds.GetOutliers(0.95);
122                if (outliers != null)
123                {
124                    List<KeyValuePair<DateTime, double>> recentOutliers = outliers.Where(
125                        sample => (DateTime.Now - sample.Key).TotalSeconds <= 60).ToList();
126                    if (recentOutliers.Count >= 5)
127                    {
128                        Speeds.Reset();
129                        recentOutliers.ForEach(sample => Speeds.Add(sample.Value));
130                    }
131                }
132
133                Speeds.Add(speed);
134                PredictedSpeed = Speeds.Predict(0.50);
135            }
136        }
137
138        /// <summary>
139        /// Predicts the speed of the operation, given the current samples.
140        /// </summary>
141        protected Interval PredictedSpeed
142        {
143            get;
144            private set;
145        }
146
147        /// <summary>
148        /// The speed sampler.
149        /// </summary>
150        private Sampler Speeds = new Sampler();
151    }
152
153    /// <summary>
154    /// Manages progress based only on one input, set through the Completed and Total
155    /// properties.
156    /// </summary>
157    public class ProgressManager : ProgressManagerBase
158    {
159        /// <summary>
160        /// Marks this task's progress as indeterminate.
161        /// </summary>
162        public void MarkIndeterminate()
163        {
164            progressIndeterminate = true;
165        }
166
167        /// <summary>
168        /// Marks this task as complete.
169        /// </summary>
170        public void MarkComplete()
171        {
172            progressIndeterminate = false;
173            if (total == 0)
174                completed = total = 1;
175            else
176                completed = total;
177        }
178
179        /// <summary>
180        /// Gets or sets the number of work units already completed.
181        /// </summary>
182        /// <remarks>This unsets the Indeterminate flag for the progress of this Task.</remarks>
183        public long Completed
184        {
185            get
186            {
187                return completed;
188            }
189            set
190            {
191                if (value > Total)
192                    throw new ArgumentOutOfRangeException("value", value, "The Completed " +
193                        "property of the Progress Manager cannot exceed the total work units for " +
194                        "the task.");
195
196                completed = value;
197                progressIndeterminate = false;
198            }
199        }
200
201        /// <summary>
202        /// Gets or sets the total number of work units that this task has.
203        /// </summary>
204        public long Total
205        {
206            get
207            {
208                return total;
209            }
210            set
211            {
212                if (value < Completed)
213                    throw new ArgumentOutOfRangeException("value", value, "The Total property " +
214                        "of the Progress Manager must be greater than or equal to the completed " +
215                        "work units for the task.");
216                if (value < 0)
217                    throw new ArgumentOutOfRangeException("value", value, "The Total property " +
218                        "of the Progress Manager must be a positive integer");
219
220                total = value;
221            }
222        }
223
224        public override float Progress
225        {
226            get
227            {
228                if (Total == 0)
229                    return 0.0f;
230                else if (ProgressIndeterminate)
231                    return float.NaN;
232
233                return (float)((double)Completed / Total);
234            }
235        }
236
237        public override bool ProgressIndeterminate
238        {
239            get
240            {
241                return progressIndeterminate;
242            }
243        }
244
245        /// <summary>
246        /// Stores whether the progress of the current task cannot be determined.
247        /// </summary>
248        /// <see cref="ProgressIndeterminate"/>
249        private bool progressIndeterminate;
250
251        public override float Speed
252        {
253            get
254            {
255                if ((DateTime.Now - lastSpeedCalc).Seconds <= 1 && lastSpeed != 0)
256                    return lastSpeed;
257
258                //Calculate how much time has passed
259                double timeElapsed = (DateTime.Now - lastSpeedCalc).TotalSeconds;
260                if (timeElapsed == 0.0)
261                    return 0;
262
263                //Then compute the speed of the calculation
264                long progressDelta = Completed - lastCompleted;
265                float currentSpeed = (float)(progressDelta / timeElapsed / total);
266                lastSpeedCalc = DateTime.Now;
267                lastCompleted = Completed;
268
269                //We won't update the speed of the task if the current speed is within
270                //the lower and upper prediction interval.
271                Interval interval = PredictedSpeed;
272                if (interval != null)
273                {
274                    if (currentSpeed < interval.Minimum)
275                    {
276                        Restart();
277                        lastSpeed = currentSpeed;
278                    }
279                    else if (currentSpeed > interval.Maximum)
280                    {
281                        Restart();
282                        lastSpeed = currentSpeed;
283                    }
284                    else if (lastSpeed == 0.0f)
285                    {
286                        lastSpeed = currentSpeed;
287                    }
288                }
289
290                SampleSpeed(currentSpeed);
291                return lastSpeed;
292            }
293        }
294
295        public override TimeSpan TimeLeft
296        {
297            get
298            {
299                float speed = Speed;
300                if (speed == 0.0)
301                    return TimeSpan.MinValue;
302
303                return TimeSpan.FromSeconds((1.0f - Progress) / speed);
304            }
305        }
306
307        /// <summary>
308        /// The last time a speed calculation was computed so that speed is not
309        /// computed too often.
310        /// </summary>
311        private DateTime lastSpeedCalc;
312
313        /// <summary>
314        /// The amount of the operation completed at the last speed computation.
315        /// </summary>
316        private long lastCompleted;
317
318        /// <summary>
319        /// The last calculated speed of the operation.
320        /// </summary>
321        private float lastSpeed;
322
323        /// <summary>
324        /// The backing field for <see cref="Completed"/>
325        /// </summary>
326        private long completed;
327
328        /// <summary>
329        /// The backing field for <see cref="Total"/>
330        /// </summary>
331        private long total;
332    }
333
334    /// <summary>
335    /// Manages progress based on sub-tasks.
336    /// </summary>
337    public abstract class ChainedProgressManager : ProgressManagerBase
338    {
339    }
340
341    /// <summary>
342    /// Manages progress based on sub-tasks, taking each sub-task to be a step
343    /// in which the next step will not be executed until the current step is
344    /// complete. Each step is also assign weights so that certain steps which
345    /// take more time are given a larger amount of progress-bar space for finer
346    /// grained progress reporting.
347    /// </summary>
348    public class SteppedProgressManager : ChainedProgressManager
349    {
350        /// <summary>
351        /// The class which manages the steps which comprise the overall progress.
352        /// </summary>
353        private class StepsList : IList<SteppedProgressManagerStep>
354        {
355            public StepsList(SteppedProgressManager manager)
356            {
357                List = new List<SteppedProgressManagerStep>();
358                ListLock = manager.ListLock;
359            }
360
361            #region IList<SteppedProgressManagerStep> Members
362
363            public int IndexOf(SteppedProgressManagerStep item)
364            {
365                lock (ListLock)
366                    return List.IndexOf(item);
367            }
368
369            public void Insert(int index, SteppedProgressManagerStep item)
370            {
371                lock (ListLock)
372                {
373                    List.Insert(index, item);
374                    TotalWeights += item.Weight;
375                }
376            }
377
378            public void RemoveAt(int index)
379            {
380                lock (ListLock)
381                {
382                    TotalWeights -= List[index].Weight;
383                    List.RemoveAt(index);
384                }
385            }
386
387            public SteppedProgressManagerStep this[int index]
388            {
389                get
390                {
391                    lock (ListLock)
392                        return List[index];
393                }
394                set
395                {
396                    lock (ListLock)
397                    {
398                        TotalWeights -= List[index].Weight;
399                        List[index] = value;
400                        TotalWeights += value.Weight;
401                    }
402                }
403            }
404
405            #endregion
406
407            #region ICollection<SteppedProgressManagerStep> Members
408
409            public void Add(SteppedProgressManagerStep item)
410            {
411                lock (ListLock)
412                {
413                    List.Add(item);
414                    TotalWeights += item.Weight;
415                }
416            }
417
418            public void Clear()
419            {
420                lock (ListLock)
421                {
422                    List.Clear();
423                    TotalWeights = 0;
424                }
425            }
426
427            public bool Contains(SteppedProgressManagerStep item)
428            {
429                lock (ListLock)
430                    return List.Contains(item);
431            }
432
433            public void CopyTo(SteppedProgressManagerStep[] array, int arrayIndex)
434            {
435                lock (ListLock)
436                    List.CopyTo(array, arrayIndex);
437            }
438
439            public int Count
440            {
441                get
442                {
443                    lock (ListLock) 
444                        return List.Count;
445                }
446            }
447
448            public bool IsReadOnly
449            {
450                get { return false; }
451            }
452
453            public bool Remove(SteppedProgressManagerStep item)
454            {
455                int index = List.IndexOf(item);
456                if (index != -1)
457                    TotalWeights -= List[index].Weight;
458
459                return List.Remove(item);
460            }
461
462            #endregion
463
464            #region IEnumerable<SteppedProgressManagerStep> Members
465
466            public IEnumerator<SteppedProgressManagerStep> GetEnumerator()
467            {
468                return List.GetEnumerator();
469            }
470
471            #endregion
472
473            #region IEnumerable Members
474
475            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
476            {
477                return List.GetEnumerator();
478            }
479
480            #endregion
481
482            /// <summary>
483            /// The total weights of all the steps.
484            /// </summary>
485            private float TotalWeights
486            {
487                get
488                {
489                    return totalWeights;
490                }
491                set
492                {
493                    if (value >= 1.1f || value < 0.0f)
494                        throw new ArgumentOutOfRangeException("value", "The total weights of " +
495                            "all steps in the task must be within the range [0.0, 1.0]");
496
497                    totalWeights = value;
498                }
499            }
500
501            /// <summary>
502            /// The list storing the steps for this instance.
503            /// </summary>
504            private List<SteppedProgressManagerStep> List;
505
506            /// <summary>
507            /// The lock object guarding the list against parallel writes.
508            /// </summary>
509            private object ListLock;
510
511            /// <summary>
512            /// The backing variable for the total weights of all the steps.
513            /// </summary>
514            private float totalWeights;
515        }
516
517        /// <summary>
518        /// Constructor.
519        /// </summary>
520        public SteppedProgressManager()
521        {
522            ListLock = new object();
523            Steps = new StepsList(this);
524        }
525
526        public override float Progress
527        {
528            get
529            {
530                lock (ListLock)
531                    return Steps.Sum(step => step.Progress.Progress * step.Weight);
532            }
533        }
534
535        public override bool ProgressIndeterminate
536        {
537            get
538            {
539                lock (ListLock)
540                    return Steps.Any(x => x.Progress.ProgressIndeterminate);
541            }
542        }
543
544        public override float Speed
545        {
546            get
547            {
548                if ((DateTime.Now - lastSpeedCalc).TotalSeconds < SpeedCalcInterval)
549                    return lastSpeed;
550
551                //Calculate how much time has passed
552                double timeElapsed = (DateTime.Now - lastSpeedCalc).TotalSeconds;
553
554                //Then compute the speed of the calculation
555                float currentProgress = Progress;
556                float progressDelta = currentProgress - lastCompleted;
557                float currentSpeed = (float)(progressDelta / timeElapsed);
558                lastSpeedCalc = DateTime.Now;
559                lastCompleted = Progress;
560
561                //If the progress delta is zero, it usually means that the amount
562                //completed within the calculation interval is too short -- lengthen
563                //the interval so we can get a small difference, significant to make
564                //a speed calculation. Likewise, if it is too great a difference,
565                //we need to shorten the interval to get more accurate calculations
566                if (progressDelta == 0.0)
567                    SpeedCalcInterval += SpeedCalcInterval / 3;
568                else if (progressDelta > 0.01 && SpeedCalcInterval > 6)
569                    SpeedCalcInterval -= 3;
570
571                //We won't update the speed of the task if the current speed is within
572                //the lower and upper prediction interval.
573                Interval interval = PredictedSpeed;
574                if (interval != null)
575                {
576                    if (currentSpeed < interval.Minimum)
577                    {
578                        Restart();
579                        lastSpeed = currentSpeed;
580                    }
581                    else if (currentSpeed > interval.Maximum)
582                    {
583                        Restart();
584                        lastSpeed = currentSpeed;
585                    }
586                    else if (lastSpeed == 0.0f)
587                    {
588                        lastSpeed = currentSpeed;
589                    }
590                }
591
592                SampleSpeed(currentSpeed);
593                return lastSpeed;
594            }
595        }
596
597        public override TimeSpan TimeLeft
598        {
599            get
600            {
601                float speed = Speed;
602                float remaining = 1.0f - Progress;
603
604                if (speed == 0)
605                    return TimeSpan.MinValue;
606                else if (remaining <= 0)
607                    return TimeSpan.Zero;
608
609                try
610                {
611                    return TimeSpan.FromSeconds(remaining / speed);
612                }
613                catch (OverflowException)
614                {
615                    return TimeSpan.MaxValue;
616                }
617            }
618        }
619
620        /// <summary>
621        /// The list of steps involved in completion of the task.
622        /// </summary>
623        public IList<SteppedProgressManagerStep> Steps
624        {
625            get;
626            private set;
627        }
628
629        /// <summary>
630        /// Gets the current step which is executing. This property is null if
631        /// no steps are executing (also when the task is complete)
632        /// </summary>
633        public SteppedProgressManagerStep CurrentStep
634        {
635            get
636            {
637                lock (ListLock)
638                {
639                    if (Steps.Count == 0)
640                        return null;
641
642                    foreach (SteppedProgressManagerStep step in Steps)
643                        if (step.Progress.Progress < 1.0f)
644                            return step;
645
646                    //Return the last step since we don't have any
647                    return Steps[Steps.Count - 1];
648                }
649            }
650        }
651
652        /// <summary>
653        /// The lock object guarding the list of steps against concurrent read and write.
654        /// </summary>
655        private object ListLock;
656
657        /// <summary>
658        /// The amount of time elapsed before a new speed calculation is made.
659        /// </summary>
660        private int SpeedCalcInterval = 15;
661
662        /// <summary>
663        /// The last time a speed calculation was computed so that speed is not
664        /// computed too often.
665        /// </summary>
666        private DateTime lastSpeedCalc;
667
668        /// <summary>
669        /// The amount of the operation completed at the last speed computation.
670        /// </summary>
671        private float lastCompleted;
672
673        /// <summary>
674        /// The last calculated speed of the operation.
675        /// </summary>
676        private float lastSpeed;
677    }
678
679    /// <summary>
680    /// Represents one step in the list of steps to complete.
681    /// </summary>
682    public class SteppedProgressManagerStep
683    {
684        /// <summary>
685        /// Constructor.
686        /// </summary>
687        /// <param name="progress">The <see cref="ProgressManagerBase"/> instance
688        /// which measures the progress of this step.</param>
689        /// <param name="weight">The weight of this step. The weight is a decimal
690        /// number in the range [0.0, 1.0] which represents the percentage of the
691        /// entire process this particular step is.</param>
692        public SteppedProgressManagerStep(ProgressManagerBase progress, float weight)
693            : this(progress, weight, null)
694        {
695        }
696
697        /// <summary>
698        /// Constructor.
699        /// </summary>
700        /// <param name="progress">The <see cref="ProgressManagerBase"/> instance
701        /// which measures the progress of this step.</param>
702        /// <param name="weight">The weight of this step. The weight is a decimal
703        /// number in the range [0.0, 1.0] which represents the percentage of the
704        /// entire process this particular step is.</param>
705        /// <param name="name">A user-specified value of the name of this step.
706        /// This value is not used by the class at all.</param>
707        public SteppedProgressManagerStep(ProgressManagerBase progress, float weight, string name)
708        {
709            if (float.IsInfinity(weight) || float.IsNaN(weight))
710                throw new ArgumentException("The weight of a progress manager step must be " +
711                    "a valid floating-point value.");
712
713            Progress = progress;
714            Weight = weight;
715            Name = name;
716        }
717
718        /// <summary>
719        /// The <see cref="ProgressManagerBase"/> instance which measures the
720        /// progress of the step.
721        /// </summary>
722        public ProgressManagerBase Progress
723        {
724            get;
725            set;
726        }
727
728        /// <summary>
729        /// The weight associated with this step.
730        /// </summary>
731        public float Weight
732        {
733            get;
734            private set;
735        }
736
737        /// <summary>
738        /// The name of this step.
739        /// </summary>
740        public string Name
741        {
742            get;
743            set;
744        }
745    }
746
747    /// <summary>
748    /// Manages progress based on sub-tasks, assuming each sub-task to be independent
749    /// of the rest.
750    /// </summary>
751    public class ParallelProgressManager : ChainedProgressManager
752    {
753        /// <summary>
754        /// The class which manages the progress of each dependent task.
755        /// </summary>
756        private class SubTasksList : IList<ProgressManagerBase>
757        {
758            public SubTasksList(ParallelProgressManager manager)
759            {
760                List = new List<ProgressManagerBase>();
761                ListLock = manager.TaskLock;
762            }
763
764            #region IList<SubTasksList> Members
765
766            public int IndexOf(ProgressManagerBase item)
767            {
768                lock (ListLock)
769                    return List.IndexOf(item);
770            }
771
772            public void Insert(int index, ProgressManagerBase item)
773            {
774                lock (ListLock)
775                    List.Insert(index, item);
776            }
777
778            public void RemoveAt(int index)
779            {
780                lock (ListLock)
781                    List.RemoveAt(index);
782            }
783
784            public ProgressManagerBase this[int index]
785            {
786                get
787                {
788                    lock (ListLock)
789                        return List[index];
790                }
791                set
792                {
793                    lock (ListLock)
794                        List[index] = value;
795                }
796            }
797
798            #endregion
799
800            #region ICollection<SteppedProgressManagerStep> Members
801
802            public void Add(ProgressManagerBase item)
803            {
804                lock (ListLock)
805                    List.Add(item);
806            }
807
808            public void Clear()
809            {
810                lock (ListLock)
811                    List.Clear();
812            }
813
814            public bool Contains(ProgressManagerBase item)
815            {
816                return List.Contains(item);
817            }
818
819            public void CopyTo(ProgressManagerBase[] array, int arrayIndex)
820            {
821                lock (ListLock)
822                    List.CopyTo(array, arrayIndex);
823            }
824
825            public int Count
826            {
827                get
828                {
829                    lock (ListLock) 
830                        return List.Count;
831                }
832            }
833
834            public bool IsReadOnly
835            {
836                get { return false; }
837            }
838
839            public bool Remove(ProgressManagerBase item)
840            {
841                lock (ListLock)
842                    return List.Remove(item);
843            }
844
845            #endregion
846
847            #region IEnumerable<ProgressManagerBase> Members
848
849            public IEnumerator<ProgressManagerBase> GetEnumerator()
850            {
851                return List.GetEnumerator();
852            }
853
854            #endregion
855
856            #region IEnumerable Members
857
858            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
859            {
860                return List.GetEnumerator();
861            }
862
863            #endregion
864
865            /// <summary>
866            /// The list storing the steps for this instance.
867            /// </summary>
868            private List<ProgressManagerBase> List;
869
870            /// <summary>
871            /// The lock object guarding the list from concurrent read/writes.
872            /// </summary>
873            private object ListLock;
874        }
875
876        /// <summary>
877        /// Constructor.
878        /// </summary>
879        public ParallelProgressManager()
880        {
881            Tasks = new SubTasksList(this);
882            TaskLock = new object();
883        }
884
885        public override float Progress
886        {
887            get
888            {
889                lock (TaskLock)
890                    return Tasks.Sum(task => task.Progress * (1.0f / Tasks.Count));
891            }
892        }
893
894        public override bool ProgressIndeterminate
895        {
896            get
897            {
898                lock (TaskLock)
899                    return Tasks.Any(x => x.ProgressIndeterminate);
900            }
901        }
902
903        public override float Speed
904        {
905            get
906            {
907                lock (TaskLock)
908                    return Tasks.Max(task => task.Speed);
909            }
910        }
911
912        public override TimeSpan TimeLeft
913        {
914            get
915            {
916                lock (TaskLock)
917                    return Tasks.Max(task => task.TimeLeft);
918            }
919        }
920
921        /// <summary>
922        /// Gets the list of tasks which must complete execution before the task
923        /// is completed.
924        /// </summary>
925        public IList<ProgressManagerBase> Tasks
926        {
927            get;
928            private set;
929        }
930
931        /// <summary>
932        /// The lock object guarding the list of tasks against concurrent read and write.
933        /// </summary>
934        private object TaskLock;
935    }
936
937    /// <summary>
938    /// Provides data for the Eraser.Manager.ProgressChanged event.
939    /// </summary>
940    public class ProgressChangedEventArgs : EventArgs
941    {
942        /// <summary>
943        /// Constructor.
944        /// </summary>
945        /// <param name="progress">The ProgressManagerBase object that stores the progress
946        /// for the given task.</param>
947        /// <param name="userState">A client-specified state object.</param>
948        public ProgressChangedEventArgs(ProgressManagerBase progress, object userState)
949        {
950            Progress = progress;
951            UserState = userState;
952        }
953
954        /// <summary>
955        /// The ProgressManagerBase object that stores the progress for the given
956        /// task.
957        /// </summary>
958        public ProgressManagerBase Progress { get; private set; }
959
960        /// <summary>
961        /// A client-specified state object.
962        /// </summary>
963        public object UserState { get; private set; }
964    }
965
966    /// <summary>
967    /// Represents the method that will handle the ProgressChanged event from
968    /// the <see cref="ProgressManagerBase"/> class.
969    /// </summary>
970    /// <param name="sender">The source of the event.</param>
971    /// <param name="e">A <see cref="ProgressChangedEventArgs"/> event that
972    /// stores the event data.</param>
973    public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e);
974}
Note: See TracBrowser for help on using the repository browser.