Progress.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Threading;
  6. using System.Diagnostics;
  7. namespace System
  8. {
  9. /// <summary>
  10. /// Provides an IProgress{T} that invokes callbacks for each reported progress value.
  11. /// </summary>
  12. /// <typeparam name="T">Specifies the type of the progress report value.</typeparam>
  13. /// <remarks>
  14. /// Any handler provided to the constructor or event handlers registered with
  15. /// the <see cref="ProgressChanged"/> event are invoked through a
  16. /// <see cref="System.Threading.SynchronizationContext"/> instance captured
  17. /// when the instance is constructed. If there is no current SynchronizationContext
  18. /// at the time of construction, the callbacks will be invoked on the ThreadPool.
  19. /// </remarks>
  20. public class Progress<T> : IProgress<T>
  21. {
  22. /// <summary>The synchronization context captured upon construction. This will never be null.</summary>
  23. private readonly SynchronizationContext _synchronizationContext;
  24. /// <summary>The handler specified to the constructor. This may be null.</summary>
  25. private readonly Action<T> _handler;
  26. /// <summary>A cached delegate used to post invocation to the synchronization context.</summary>
  27. private readonly SendOrPostCallback _invokeHandlers;
  28. /// <summary>Initializes the <see cref="Progress{T}"/>.</summary>
  29. public Progress()
  30. {
  31. // Capture the current synchronization context.
  32. // If there is no current context, we use a default instance targeting the ThreadPool.
  33. _synchronizationContext = SynchronizationContext.Current ?? ProgressStatics.DefaultContext;
  34. Debug.Assert(_synchronizationContext != null);
  35. _invokeHandlers = new SendOrPostCallback(InvokeHandlers);
  36. }
  37. /// <summary>Initializes the <see cref="Progress{T}"/> with the specified callback.</summary>
  38. /// <param name="handler">
  39. /// A handler to invoke for each reported progress value. This handler will be invoked
  40. /// in addition to any delegates registered with the <see cref="ProgressChanged"/> event.
  41. /// Depending on the <see cref="System.Threading.SynchronizationContext"/> instance captured by
  42. /// the <see cref="Progress{T}"/> at construction, it's possible that this handler instance
  43. /// could be invoked concurrently with itself.
  44. /// </param>
  45. /// <exception cref="System.ArgumentNullException">The <paramref name="handler"/> is null (Nothing in Visual Basic).</exception>
  46. public Progress(Action<T> handler) : this()
  47. {
  48. if (handler == null) throw new ArgumentNullException(nameof(handler));
  49. _handler = handler;
  50. }
  51. /// <summary>Raised for each reported progress value.</summary>
  52. /// <remarks>
  53. /// Handlers registered with this event will be invoked on the
  54. /// <see cref="System.Threading.SynchronizationContext"/> captured when the instance was constructed.
  55. /// </remarks>
  56. public event EventHandler<T> ProgressChanged;
  57. /// <summary>Reports a progress change.</summary>
  58. /// <param name="value">The value of the updated progress.</param>
  59. protected virtual void OnReport(T value)
  60. {
  61. // If there's no handler, don't bother going through the sync context.
  62. // Inside the callback, we'll need to check again, in case
  63. // an event handler is removed between now and then.
  64. Action<T> handler = _handler;
  65. EventHandler<T> changedEvent = ProgressChanged;
  66. if (handler != null || changedEvent != null)
  67. {
  68. // Post the processing to the sync context.
  69. // (If T is a value type, it will get boxed here.)
  70. _synchronizationContext.Post(_invokeHandlers, value);
  71. }
  72. }
  73. /// <summary>Reports a progress change.</summary>
  74. /// <param name="value">The value of the updated progress.</param>
  75. void IProgress<T>.Report(T value) { OnReport(value); }
  76. /// <summary>Invokes the action and event callbacks.</summary>
  77. /// <param name="state">The progress value.</param>
  78. private void InvokeHandlers(object state)
  79. {
  80. T value = (T)state;
  81. Action<T> handler = _handler;
  82. EventHandler<T> changedEvent = ProgressChanged;
  83. if (handler != null) handler(value);
  84. if (changedEvent != null) changedEvent(this, value);
  85. }
  86. }
  87. /// <summary>Holds static values for <see cref="Progress{T}"/>.</summary>
  88. /// <remarks>This avoids one static instance per type T.</remarks>
  89. internal static class ProgressStatics
  90. {
  91. /// <summary>A default synchronization context that targets the ThreadPool.</summary>
  92. internal static readonly SynchronizationContext DefaultContext = new SynchronizationContext();
  93. }
  94. }