diff --git a/Nuclex.Windows.Forms.csproj b/Nuclex.Windows.Forms.csproj
index 3f92341..7607c03 100644
--- a/Nuclex.Windows.Forms.csproj
+++ b/Nuclex.Windows.Forms.csproj
@@ -62,6 +62,16 @@
false
AssemblyInfo
+
+ Component
+ false
+ AsyncProgressBar
+
+
+ AsyncProgressBar.cs
+ false
+ AsyncProgressBar.Designer
+
Component
false
@@ -99,8 +109,6 @@
TrackingBar.cs
- false
- TrackingBar.Designer
@@ -111,10 +119,8 @@
- Designer
- false
- ProgressReporterForm
ProgressReporterForm.cs
+ Designer
diff --git a/Source/AsyncProgressBar/AsyncProgressBar.Designer.cs b/Source/AsyncProgressBar/AsyncProgressBar.Designer.cs
new file mode 100644
index 0000000..76d4688
--- /dev/null
+++ b/Source/AsyncProgressBar/AsyncProgressBar.Designer.cs
@@ -0,0 +1,33 @@
+namespace Nuclex.Windows.Forms {
+
+ partial class AsyncProgressBar {
+
+ /// Required designer variable.
+ private System.ComponentModel.IContainer components = null;
+
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ ///
+ protected override void Dispose(bool disposing) {
+ if(disposing && (components != null)) {
+ components.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent() {
+ components = new System.ComponentModel.Container();
+ }
+
+ #endregion
+ }
+
+} // namespace Nuclex.Windows.Forms
diff --git a/Source/AsyncProgressBar/AsyncProgressBar.cs b/Source/AsyncProgressBar/AsyncProgressBar.cs
new file mode 100644
index 0000000..607ceb8
--- /dev/null
+++ b/Source/AsyncProgressBar/AsyncProgressBar.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Text;
+using System.Windows.Forms;
+using System.Threading;
+
+namespace Nuclex.Windows.Forms {
+
+ /// Progress bar with optimized multi-threading behavior
+ ///
+ /// If a background thread is generating lots of progress updates, using synchronized
+ /// calls can drastically reduce performance. This progress bar optimizes this case
+ /// by performing the update asynchronously and keeping only the most recent update
+ /// when multiple updates arrive while the asynchronous update call is still running.
+ ///
+ public partial class AsyncProgressBar : ProgressBar {
+
+ /// Initializes a new asynchronous progress bar
+ public AsyncProgressBar() {
+ InitializeComponent();
+
+ this.updateProgressDelegate = new MethodInvoker(updateProgress);
+ this.Disposed += new EventHandler(progressBarDisposed);
+
+ Interlocked.Exchange(ref this.newProgress, -1.0f);
+ }
+
+ /// Called when the progress bar is being disposed
+ /// Progress bar that is being disposed
+ /// Not used
+ private void progressBarDisposed(object sender, EventArgs arguments) {
+
+ // Since this has to occur in the UI thread, there's no way that updateProgress()
+ // could be executing just now. But the final call to updateProgress() will not
+ // have EndInvoke() called on it yet, so we do this here before the control
+ // is finally disposed.
+ if(this.progressUpdateAsyncResult != null)
+ EndInvoke(this.progressUpdateAsyncResult);
+
+ // CHECK: This method is only called on an explicit Dispose() of the control.
+ // Microsoft officially states that it's allowed to call Control.BeginInvoke()
+ // without calling Control.EndInvoke(), so this code is quite correct,
+ // but is it also clean? :>
+
+ }
+
+ /// Asynchronously updates the value to be shown in the progress bar
+ /// New value to set the progress bar to
+ public void AsyncSetValue(float value) {
+
+ // Update the value to be shown on the progress bar. If this happens multiple
+ // times, that's not a problem, the progress bar updates as fast as it can
+ // and always tries to show the most recent value assigned.
+ float oldValue = Interlocked.Exchange(ref this.newProgress, value);
+
+ // If the previous value was -1, the UI thread has already taken out the most recent
+ // value and assigned it (or is about to assign it) to the progress bar control.
+ // In this case, we'll wait until the current update has completed and immediately
+ // begin the next update - since we know that the value the UI thread has extracted
+ // is no longer the most recent one.
+ if(oldValue == -1.0f) {
+ if(this.progressUpdateAsyncResult != null)
+ EndInvoke(this.progressUpdateAsyncResult);
+
+ this.progressUpdateAsyncResult = BeginInvoke(this.updateProgressDelegate);
+ }
+
+ }
+
+ /// Synchronously updates the value visualized in the progress bar
+ private void updateProgress() {
+
+ int minimum = base.Minimum;
+ int maximum = base.Maximum;
+
+ // Take out the most recent value that has been given to the asynchronous progress
+ // bar up until now and replace it by -1. This enables the updater to see when
+ // the update has actually been performed and whether it needs to start a new
+ // invocation to ensure the most recent value will remain at the end.
+ float progress = Interlocked.Exchange(ref this.newProgress, -1.0f);
+
+ // Convert the value to the progress bar's configured range and assign it
+ // to the progress bar
+ int value = (int)(progress * (maximum - minimum)) + minimum;
+ base.Value = Math.Min(Math.Max(value, minimum), maximum);
+
+ }
+
+ /// New progress being assigned to the progress bar
+ private float newProgress;
+ /// Delegate for the progress update method
+ private MethodInvoker updateProgressDelegate;
+ /// Async result for the invoked control state update method
+ private volatile IAsyncResult progressUpdateAsyncResult;
+
+ }
+
+} // namespace Nuclex.Windows.Forms
diff --git a/Source/ProgressReporter/ProgressReporterForm.cs b/Source/ProgressReporter/ProgressReporterForm.cs
index 736cb06..8016635 100644
--- a/Source/ProgressReporter/ProgressReporterForm.cs
+++ b/Source/ProgressReporter/ProgressReporterForm.cs
@@ -69,7 +69,7 @@ namespace Nuclex.Windows.Forms {
// Only allow the form to close when the form is ready to close and the
// progression being tracked has also finished.
- e.Cancel = (this.state < 2);
+ e.Cancel = (Thread.VolatileRead(ref this.state) < 2);
}
///
@@ -99,7 +99,7 @@ namespace Nuclex.Windows.Forms {
if(!progression.Ended)
ShowDialog();
- // We're done, unsubscribe from the progression's events
+ // We're done, unsubscribe from the progression's events again
progression.AsyncProgressUpdated -= this.asyncProgressUpdatedDelegate;
progression.AsyncEnded -= this.asyncEndedDelegate;
@@ -172,7 +172,7 @@ namespace Nuclex.Windows.Forms {
int progress = (int)(this.currentProgress * (max - min)) + min;
// Update the control
- this.progressBar.Value = Math.Min(Math.Max(progress, min), max);;
+ this.progressBar.Value = Math.Min(Math.Max(progress, min), max);
// Assigning the value sends PBM_SETPOS to the control which,
// according to MSDN, already causes a redraw!
@@ -224,19 +224,18 @@ namespace Nuclex.Windows.Forms {
}
- /// Whether an update of the control state is pending
- private volatile bool progressUpdatePending;
- /// Async result for the invoked control state update method
- private volatile IAsyncResult progressUpdateAsyncResult;
- /// Most recently reported progress of the tracker
- private volatile float currentProgress;
-
/// Delegate for the asyncEnded() method
private EventHandler asyncEndedDelegate;
/// Delegate for the asyncProgressUpdated() method
private EventHandler asyncProgressUpdatedDelegate;
/// Delegate for the progress update method
private MethodInvoker updateProgressDelegate;
+ /// Whether an update of the control state is pending
+ private volatile bool progressUpdatePending;
+ /// Async result for the invoked control state update method
+ private volatile IAsyncResult progressUpdateAsyncResult;
+ /// Most recently reported progress of the tracker
+ private volatile float currentProgress;
/// Whether the form can be closed and should be closed
///
/// 0: Nothing happened yet