Extracted and improved the code used to asynchronously update a progress bar without blocking in order to remove similar code from the progress dialog and the tracking bar
git-svn-id: file:///srv/devel/repo-conversion/nuwi@9 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
c5fba24733
commit
4734c35111
|
@ -62,6 +62,16 @@
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
<Name>AssemblyInfo</Name>
|
<Name>AssemblyInfo</Name>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\AsyncProgressBar\AsyncProgressBar.cs">
|
||||||
|
<SubType>Component</SubType>
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>AsyncProgressBar</Name>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\AsyncProgressBar\AsyncProgressBar.Designer.cs">
|
||||||
|
<DependentUpon>AsyncProgressBar.cs</DependentUpon>
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>AsyncProgressBar.Designer</Name>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\ContainerListView\ContainerListView.cs">
|
<Compile Include="Source\ContainerListView\ContainerListView.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
@ -99,8 +109,6 @@
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Source\TrackingBar\TrackingBar.Designer.cs">
|
<Compile Include="Source\TrackingBar\TrackingBar.Designer.cs">
|
||||||
<DependentUpon>TrackingBar.cs</DependentUpon>
|
<DependentUpon>TrackingBar.cs</DependentUpon>
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
|
||||||
<Name>TrackingBar.Designer</Name>
|
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -111,10 +119,8 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Source\ProgressReporter\ProgressReporterForm.resx">
|
<EmbeddedResource Include="Source\ProgressReporter\ProgressReporterForm.resx">
|
||||||
<SubType>Designer</SubType>
|
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
|
||||||
<Name>ProgressReporterForm</Name>
|
|
||||||
<DependentUpon>ProgressReporterForm.cs</DependentUpon>
|
<DependentUpon>ProgressReporterForm.cs</DependentUpon>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
|
|
33
Source/AsyncProgressBar/AsyncProgressBar.Designer.cs
generated
Normal file
33
Source/AsyncProgressBar/AsyncProgressBar.Designer.cs
generated
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
namespace Nuclex.Windows.Forms {
|
||||||
|
|
||||||
|
partial class AsyncProgressBar {
|
||||||
|
|
||||||
|
/// <summary>Required designer variable.</summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>Clean up any resources being used.</summary>
|
||||||
|
/// <param name="disposing">
|
||||||
|
/// true if managed resources should be disposed; otherwise, false.
|
||||||
|
/// </param>
|
||||||
|
protected override void Dispose(bool disposing) {
|
||||||
|
if(disposing && (components != null)) {
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Component Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent() {
|
||||||
|
components = new System.ComponentModel.Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Windows.Forms
|
101
Source/AsyncProgressBar/AsyncProgressBar.cs
Normal file
101
Source/AsyncProgressBar/AsyncProgressBar.cs
Normal file
|
@ -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 {
|
||||||
|
|
||||||
|
/// <summary>Progress bar with optimized multi-threading behavior</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
public partial class AsyncProgressBar : ProgressBar {
|
||||||
|
|
||||||
|
/// <summary>Initializes a new asynchronous progress bar</summary>
|
||||||
|
public AsyncProgressBar() {
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.updateProgressDelegate = new MethodInvoker(updateProgress);
|
||||||
|
this.Disposed += new EventHandler(progressBarDisposed);
|
||||||
|
|
||||||
|
Interlocked.Exchange(ref this.newProgress, -1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Called when the progress bar is being disposed</summary>
|
||||||
|
/// <param name="sender">Progress bar that is being disposed</param>
|
||||||
|
/// <param name="arguments">Not used</param>
|
||||||
|
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? :>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Asynchronously updates the value to be shown in the progress bar</summary>
|
||||||
|
/// <param name="value">New value to set the progress bar to</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Synchronously updates the value visualized in the progress bar</summary>
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>New progress being assigned to the progress bar</summary>
|
||||||
|
private float newProgress;
|
||||||
|
/// <summary>Delegate for the progress update method</summary>
|
||||||
|
private MethodInvoker updateProgressDelegate;
|
||||||
|
/// <summary>Async result for the invoked control state update method</summary>
|
||||||
|
private volatile IAsyncResult progressUpdateAsyncResult;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Windows.Forms
|
|
@ -69,7 +69,7 @@ namespace Nuclex.Windows.Forms {
|
||||||
|
|
||||||
// Only allow the form to close when the form is ready to close and the
|
// Only allow the form to close when the form is ready to close and the
|
||||||
// progression being tracked has also finished.
|
// progression being tracked has also finished.
|
||||||
e.Cancel = (this.state < 2);
|
e.Cancel = (Thread.VolatileRead(ref this.state) < 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -99,7 +99,7 @@ namespace Nuclex.Windows.Forms {
|
||||||
if(!progression.Ended)
|
if(!progression.Ended)
|
||||||
ShowDialog();
|
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.AsyncProgressUpdated -= this.asyncProgressUpdatedDelegate;
|
||||||
progression.AsyncEnded -= this.asyncEndedDelegate;
|
progression.AsyncEnded -= this.asyncEndedDelegate;
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ namespace Nuclex.Windows.Forms {
|
||||||
int progress = (int)(this.currentProgress * (max - min)) + min;
|
int progress = (int)(this.currentProgress * (max - min)) + min;
|
||||||
|
|
||||||
// Update the control
|
// 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,
|
// Assigning the value sends PBM_SETPOS to the control which,
|
||||||
// according to MSDN, already causes a redraw!
|
// according to MSDN, already causes a redraw!
|
||||||
|
@ -224,19 +224,18 @@ namespace Nuclex.Windows.Forms {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Whether an update of the control state is pending</summary>
|
|
||||||
private volatile bool progressUpdatePending;
|
|
||||||
/// <summary>Async result for the invoked control state update method</summary>
|
|
||||||
private volatile IAsyncResult progressUpdateAsyncResult;
|
|
||||||
/// <summary>Most recently reported progress of the tracker</summary>
|
|
||||||
private volatile float currentProgress;
|
|
||||||
|
|
||||||
/// <summary>Delegate for the asyncEnded() method</summary>
|
/// <summary>Delegate for the asyncEnded() method</summary>
|
||||||
private EventHandler asyncEndedDelegate;
|
private EventHandler asyncEndedDelegate;
|
||||||
/// <summary>Delegate for the asyncProgressUpdated() method</summary>
|
/// <summary>Delegate for the asyncProgressUpdated() method</summary>
|
||||||
private EventHandler<ProgressUpdateEventArgs> asyncProgressUpdatedDelegate;
|
private EventHandler<ProgressUpdateEventArgs> asyncProgressUpdatedDelegate;
|
||||||
/// <summary>Delegate for the progress update method</summary>
|
/// <summary>Delegate for the progress update method</summary>
|
||||||
private MethodInvoker updateProgressDelegate;
|
private MethodInvoker updateProgressDelegate;
|
||||||
|
/// <summary>Whether an update of the control state is pending</summary>
|
||||||
|
private volatile bool progressUpdatePending;
|
||||||
|
/// <summary>Async result for the invoked control state update method</summary>
|
||||||
|
private volatile IAsyncResult progressUpdateAsyncResult;
|
||||||
|
/// <summary>Most recently reported progress of the tracker</summary>
|
||||||
|
private volatile float currentProgress;
|
||||||
/// <summary>Whether the form can be closed and should be closed</summary>
|
/// <summary>Whether the form can be closed and should be closed</summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 0: Nothing happened yet
|
/// 0: Nothing happened yet
|
||||||
|
|
Loading…
Reference in New Issue
Block a user