WPF progress bar and multithreading
This article will use a progress bar control as an example to introduce the multithreading of WPF. The example of the progress bar can give us a comprehensive understanding of the characteristics of WPF multithreading.
When a user downloads something or loads a large amount of data in our application, it will inevitably require the user to wait a long time. At this time, we need a progress bar to reflect the progress to the user in real time, so as to avoid the user thinking that the program is crashing. A series of honey juice operations.
So, let's go to a progress bar first.
XAML code:
<Grid Margin="100,0">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<ProgressBar
Name="ProgressBar"
Grid.Row="0"
Width="580"
Height="30"
Maximum="100"
Minimum="0" />
<DockPanel Grid.Row="1" LastChildFill="False">
<Button
Width="100"
Height="30"
Click="Download_OnClick"
Content="Download"
DockPanel.Dock="Left" />
</DockPanel>
</Grid>
There are many ways to control the progress of the progress bar, but this article mainly introduces 4 representative ones . Used to introduce how to use multithreading in WPF.
One, single thread (failure)
Background code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// Download按钮点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Download_OnClick(object sender, RoutedEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
ProgressBar.Value = i;
Thread.Sleep(100);
}
}
}
This method obviously fails to implement the progress bar, because it is executed under the UI thread and the UI thread is stuck when the method is running, and it will be reflected on the screen only when the method is finished.
Therefore, you will see the progress bar suddenly change from 0 to 100, and you cannot see the intermediate process.
Second, use Task class multithreading to implement progress bar (failure)
Using the UI thread will cause the program to freeze. It is easy for everyone to think, then use thread control. This will not affect the UI thread.
That's right, you can only use multi-threading at this time, so there is the following code.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// Download按钮点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Download_OnClick(object sender, RoutedEventArgs e)
{
Task task = new Task(TaskMethod);
task.Start();
}
private void TaskMethod()
{
for (int i = 1; i <= 100; i++)
{
ProgressBar.Value = i;
Thread.Sleep(50);
}
}
}
Using the above code will undoubtedly run abnormally and cause the program to end, because there is no progress bar in the new thread, and forcibly calling an object that cannot be found in the thread will cause an error. Therefore, do not try to access application objects in the Task thread.
Three, use Dispatcher to execute threads
To manipulate application objects in threads, you can use the Dispatcher
objects of the current application .
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// Download按钮点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Download_OnClick(object sender, RoutedEventArgs e)
{
Task task = new Task(TaskMethod);
task.Start();
}
private void TaskMethod()
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
Dispatcher.BeginInvoke((ThreadStart)delegate
{
ProgressBar.Value = i;
}, DispatcherPriority.Normal);
}
}
}
After Dispatcher Gets the current program, using the BeginInvoke(DispatcherPriority, Delegate)
asynchronous method, asynchronous method calls the delegate to implement the calling application objects in other threads.
This method has two parameters::
DispatcherPriority
Used to indicate the priority of the thread, generally not used much, the higher priority is executed first.
Delegate
: Delegate, the BeginInvoke() method will schedule the incoming method as the task of the scheduler. The scheduler will then execute this method.
Finally, the above three methods are weak. In fact, using the BackgroundWorker component can match the function of the progress bar almost perfectly.
Four, use BackgroundWorker to achieve progress bar
For ease of use, you can put it BackgroundWorker
as a resource in the window Resources
.
Attributes
1.WorkerReportsProgress
When this property is set to True, the progress update method will be triggered.
2.WorkerSupportsCancellation
When this property is set to True, the cancel method will be triggered.
event
1.DoWork
Add the method that needs to be executed asynchronously DoWork
, and BackgroundWorker
the event will be triggered when you work.
2.ProgressChanged
Add the method is executed when progress updates into ProgressChanged
, when WorkerReportsProgress = True
when the event is triggered when a progress update.
3.RunWorkerCompleted
Add the feedback method at the end of the RunWorkerCompleted
work, and the event will be triggered at the end of the work.
method
1.RunWorkerAsync(object argument)
object argument
: Pass in an object so that it can be used in asynchronous threads.
Start the asynchronous operation.
2.ProgressChanged(int percentProgress)
int percentProgress
: Pass in a value to indicate the current progress
Trigger RunWorkerCompleted
event
<Window
x:Class="ThreadDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=System"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ThreadDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="300"
mc:Ignorable="d">
<Window.Resources>
<componentModel:BackgroundWorker
x:Name="Worker"
x:Key="Worker"
DoWork="Worker_OnDoWork"
ProgressChanged="Worker_OnProgressChanged"
RunWorkerCompleted="Worker_OnRunWorkerCompleted"
WorkerReportsProgress="True"
WorkerSupportsCancellation="True" />
</Window.Resources>
<Grid Margin="100,0">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<ProgressBar
Name="ProgressBar"
Grid.Row="0"
Width="580"
Height="30"
Maximum="100"
Minimum="0" />
<DockPanel Grid.Row="1" LastChildFill="False">
<Button
Width="100"
Height="30"
Click="Download_OnClick"
Content="Download"
DockPanel.Dock="Left" />
<Button
Width="100"
Height="30"
Click="Cancel_OnClick"
Content="Cancel"
DockPanel.Dock="Right" />
</DockPanel>
</Grid>
</Window>
public partial class MainWindow : Window
{
private BackgroundWorker worker;
public MainWindow()
{
InitializeComponent();
worker = (BackgroundWorker)FindResource("Worker");
}
/// <summary>
/// Download按钮点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Download_OnClick(object sender, RoutedEventArgs e)
{
worker?.RunWorkerAsync(ProgressBar);
}
/// <summary>
/// Cancel按钮点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel_OnClick(object sender, RoutedEventArgs e)
{
worker?.CancelAsync();
}
/// <summary>
/// 线程工作方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Worker_OnDoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
worker.ReportProgress(i);
Thread.Sleep(100);
}
}
/// <summary>
/// 进度改变方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Worker_OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBar.Value = e.ProgressPercentage;
}
/// <summary>
/// 工作完成方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Worker_OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("已取消");
return;
}
MessageBox.Show("下载完成");
}
}
As shown in the code,
1. Initialize the BackgroundWorker object
In this example, we initialize the BackgroundWorker
object in the constructor .
2. Start performing asynchronous operations
In the Download button click event, BackgroundWorker的RunWorkerAsync(object argument)
run asynchronous threads with the help of methods.
Note: The methods added in Dowork will be executed in asynchronous threads. So remember that you cannot call the application object in the Dowork event.
Dowork
The object that needs to be called in the event can be passed in as a parameter.
3. Real-time feedback progress to the form
In the Dowork
event, you can call BackgroundWorker
a ReportProgress(int percentProgress)
method at any time to feed back the progress to the form.
The method is triggered when this method is called, Worker_OnProgressChanged()
and the window is updated in real time to report the progress to the user. Because this method is in the UI thread, the application object can be called arbitrarily.
4. Method end result feedback
DoWork
After the event execution ends, an RunWorkerCompleted
event will be triggered to indicate the end of the thread execution. In this method, you can use RunWorkerCompletedEventArgs
parameters to determine whether the thread execution status is normally ended or cancelled, etc., to decide how to update the form.
5. Cancel the thread
BackgroundWorker
The CancelAsync()
method called can end the thread at any time.
Although it is the end of the thread, but in fact it will not automatically end, but set the BackgroundWorker的CancellationPending
attribute to True
mark the thread as canceled.
The developer should DoWork
judge the CancellationPending
attribute in the method to decide whether to end the thread. It should be noted that the RunWorkerCompleted
event will still be triggered after setting it to the canceled state. The property that
needs to be set to pass the state to the event, and finally in the end method, it is judged that the property has decided how to feed back the result to the form.DoWorkEventArgs
Cancel
True
CancellationPending
Above, the progress bar can be implemented very conveniently by using BackgroundWorker. Of course, in addition to this, you can use this class to implement any asynchronous method, feedback progress and cancel at any time.
Does it help you? Like it~