WPF progress bar and multithreading

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>

Progress bar screen
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 Dispatcherobjects 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::
DispatcherPriorityUsed 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 BackgroundWorkeras 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 BackgroundWorkerthe event will be triggered when you work.

2.ProgressChanged

Add the method is executed when progress updates into ProgressChanged, when WorkerReportsProgress = Truewhen the event is triggered when a progress update.

3.RunWorkerCompleted

Add the feedback method at the end of the RunWorkerCompletedwork, 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 RunWorkerCompletedevent

<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 BackgroundWorkerobject 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.

DoworkThe 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 Doworkevent, you can call BackgroundWorkera 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

DoWorkAfter the event execution ends, an RunWorkerCompletedevent will be triggered to indicate the end of the thread execution. In this method, you can use RunWorkerCompletedEventArgsparameters to determine whether the thread execution status is normally ended or cancelled, etc., to decide how to update the form.

5. Cancel the thread

BackgroundWorkerThe 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的CancellationPendingattribute to Truemark the thread as canceled.

The developer should DoWorkjudge the CancellationPendingattribute in the method to decide whether to end the thread. It should be noted that the RunWorkerCompletedevent 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.DoWorkEventArgsCancelTrueCancellationPending

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~

Guess you like

Origin blog.csdn.net/qq_42068856/article/details/109015792