WPF的进度条与多线程

WPF的进度条与多线程

本文将用一个进度条控件作为例子来介绍WPF的多线程,进度条的例子可以较全面的让我们认识WPF多线程的特点。

当用户在我们的应用程序下载东西或者加载大量数据的时候,不可避免需要用户等待一段较长的时间,这时候,我们需要一个进度条来实时反映进度给用户,避免用户以为程序死机而进行的一系列蜜汁操作。
那么,先让我们上一个进度条吧。

XAML代码:

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

进度条画面
有多种方法可以控制进度条的进度,但本文中主要介绍具有代表性的4种。用来介绍如何在WPF种使用多线程。

一、单线程(失败)

后台代码:

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);
        }
    }

}

这种方法实现进度条显然是失败的,因为在UI线程下执行,方法运行时UI线程卡死,只有当方法运行结束后才会反映到画面。
所以,你会看到进度条从0突然变成100,看不到中间过程。

二、使用Task类多线程实现进度条(失败)

使用UI线程会导致程序卡死,大家很容易想到,那就用线程控制呗。这样就不会影响UI线程了。
没错,此时只能使用多线程来实现,因此就有了下面这段代码。

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);
        }
    }

}

使用上面这段代码无疑会运行异常导致程序结束,因为在新线程种并没有进度条的对象,强行调用线程中找不到的对象会导致报错。因此,不要试图在Task线程中访问应用程序对象。

三、使用Dispatcher来执行线程

想要在线程中操作应用程序对象,可以使用当前应用程序的Dispatcher对象。

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);
        }
    }

}

获取当前程序的Dispatcher后,使用 BeginInvoke(DispatcherPriority, Delegate)执行异步方法,在异步方法中会调用委托来实现在其他线程中调用应用程序对象。
该方法有两个参数:
DispatcherPriority:用于表示该线程的优先级,一般不怎么用,优先级高的先执行。
Delegate:委托,BeginInvoke()方法会将传入的方法安排为调度程序的任务。之后调度程序会执行这个方法。

最后,以上三种方法都弱爆了。实际上,使用BackgroundWorker组件可以近乎完美的匹配进度条的功能。

四、使用BackgroundWorker实现进度条

为了方便使用,可以把BackgroundWorker作为资源放进窗口的Resources里。

属性
1.WorkerReportsProgress

该属性设为True时,才会触发进度更新方法。

2.WorkerSupportsCancellation

该属性设为True时,才会触发取消方法。

事件
1.DoWork

将需要异步执行的方法,添加进DoWork中,BackgroundWorker工作时会触发该事件。

2.ProgressChanged

将进度更新时执行的方法添加进ProgressChanged中,当WorkerReportsProgress = True时,进度更新时会触发该事件。

3.RunWorkerCompleted

将工作结束时的反馈方法添加进RunWorkerCompleted中,工作结束时会触发该事件。

方法
1.RunWorkerAsync(object argument)

object argument:传入一个对象,使其可以在异步线程中使用。

开始执行异步操作。

2.ProgressChanged(int percentProgress)

int percentProgress:传入一个数值表示当前进度

触发RunWorkerCompleted事件

<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("下载完成");
    }
}

如代码所示,

1.初始化BackgroundWorker对象

本例中我们在构造方法里初始化BackgroundWorker对象。

2.开始执行异步操作

在Download按钮点击事件中借助BackgroundWorker的RunWorkerAsync(object argument)方法运行异步线程。

注意:Dowork中添加的方法将会在异步线程中执行。因此切记在Dowork事件中不能调用应用程序对象。

需要在Dowork事件中调用的对象可作为参数传入。

3.实时反馈进度到窗体

Dowork事件中可以随时调用BackgroundWorkerReportProgress(int percentProgress)方法来反馈进度到窗体。
该方法被调用时会触发Worker_OnProgressChanged()方法,实时更新窗口反馈进度给用户,因为该方法处于UI线程中,所以可以任意调用应用程序对象。

4.方法结束结果反馈

DoWork事件执行结束后会触发RunWorkerCompleted事件,表示线程执行结束,在该方法中可以使用RunWorkerCompletedEventArgs参数判断线程执行状态是正常结束或者被取消等等,来决定如何更新窗体。

5.取消线程

调用BackgroundWorkerCancelAsync()方法可以随时结束线程。
虽说是结束线程,但其实他并不会自动结束,而是将BackgroundWorker的CancellationPending属性设置为True,标记线程为取消状态。

开发者应在DoWork方法中自行判断CancellationPending属性来决定是否要结束线程。需要注意的是,设为取消状态后依然会触发RunWorkerCompleted事件,
需要将DoWorkEventArgsCancel属性设置为True,将状态传递到CancellationPending事件中,最终在结束方法里判断该属性已决定如何反馈结果到窗体。

以上,使用BackgroundWorker可以十分方便的实现进度条。当然除此之外,可以使用该类实现任何异步方法,同时反馈进度以及随时取消。

对你有帮助吗?点个赞吧~

猜你喜欢

转载自blog.csdn.net/qq_42068856/article/details/109015792