WPF 多线程

简介

这是一篇《WPF编程宝典》的读书笔记。

Dispatcher

调度程序(dispatcher)管理在WPF应用程序中发生的操作。调度程序拥有应用程序线程,并管理工作项队列。当应用程序运行时,调度程序接受新的工作请求,并且一次执行一个任务

从技术角度看,当在新线程中第一次实例化DispatcherObject类的派生类时,会创建调度程序。如果创建相互独立的线程,并用他们显示相互独立的窗口,最终将创建多个调度程序。然而,大多数应用程序都保持简单方式,并坚持使用一个用户界面线程和一个调度程序。然后,它们使用多线程管理数据操作和其他后台任务。

可使用静态的Dispatcher.CurrentDispatcher属性检索当前线程的调度程序。使用这个Dispatcher对象,可关联事件处理程序以相应未处理的异常,或当关闭调度程序时进行相应。也可以获取调度程序控制的System.Threading.Thread的引用,关闭调度程序或将代码封送(marshal)到正确的线程。

DispatcherObject类

名称 说明
Dispatcher 返回管理该对象的调度程序
CheckAccess() 如果代码在正确的线程上使用对象,就返回true,否则返回false
VerifyAccess() 如果代码在正确 的线程上使用对象,就什么也不做,否则抛出InvalidOperationException异常

示例:下面的代码通过创建新的System.Therading.Thread对象来响应按钮单击。然后使用创建的线程加载少量代码来改变当前窗口中的一个文本框:

如下代码注定会失败,UpdateTextWrong()方法将在新线程上执行,并且不允许这个新线程访问WPF对象。在本例中,TextBox对象通过调用VerifyAccess()方法捕获这一非法操作,并抛出InvalidOperationException异常。

//错误示范:
private void btn1_Click(object sender, RoutedEventArgs e)
{
    Thread thread = new Thread(UpdateTextWrong);
    thread.Start();
}
private void UpdateTextWrong()
{
    //模拟某项工作在2秒延迟的情况下进行
    Thread.Sleep(TimeSpan.FromSeconds(2));
    text1.Text = "你正在学习WPF! Invoke"; 
}

为改变上面的代码,需要获取拥有TextBox对象的调度程序的引用(这个调度程序也拥有应用程序中的窗口和所有其他WPF对象)。一旦访问这个调度程序,就可以调用Dispatcher.Invoke()方法将一些代码封送到调度程序线程。本质上BeginInvoke()方法会将代码安排为调度程序的任务,然后调度程序会执行这些代码。

//正确示范:
private void btn1_Click(object sender, RoutedEventArgs e)
{
    Thread thread = new Thread(UpdateTextWrong);
    thread.Start();
}
private void UpdateTextWrong()
{
    //模拟某项工作在2秒延迟的情况下进行
    Thread.Sleep(TimeSpan.FromSeconds(2));
    //从当前窗口获取调度程序,并使用它来调用
    //更新代码
    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
    {
        text1.Text = "你正在学习WPF! Invoke";
    }));  
}

Dispatcher.BeginInvoke()方法具有两个参数:

  1. 第一个参数指示任务的优先级。在大多数情况下会使用DispatcherPriority.Normal,但如果任务不需要被立即执行完成,也可以使用更低的优先级,并且指导调度程序没有其他工作时才会执行该任务。输入消息(如按键)推荐使用更高优先级,否则会感觉应用程序的运行时缓慢的。
  2. 第二个参数时指向一个方法的委托,该方法具有希望执行的代码。这个方法可以在代码中的其他地方定义,也可以使用匿名方法在内部定义代码。
  3. BeginInvoke()方法还有返回值,返回一个DispatcherOperation对象,通过该对象可跟踪封送操作的状态,并确定代码何使已实际执行完毕,然而,很少使用DispatcherOperation对象,因为传递到BeginInvoke()的方法应当只需很短的时间就可以执行完毕。

注意:如果执行耗时的后台操作,就需要在单独的线程中执行这个操作,然后将操作结果封送到调度程序线程(在此更新用户界面或修改共享对象)。在传递给BeginInvoke()的方法中执行耗时的代码是不合理的。例如,下面稍微重新安排的代码虽然能够工作,但并不合理:

private void UpdateTextWrong2()
{
    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
    {
        Thread.Sleep(TimeSpan.FromSeconds(5));
        text1.Text = "你正在学习WPF! BeginInvoke";
    }));

调度程序还提供了Invoke()方法,与BeginInvoke()方法类似,Invoke()方法将指定的代码封送到调度程序线程,但与BeginInvoke()方法不同,Invoke()方法会拖延线程指导调用程序执行您指定的代码。如果需要暂停异步操作指导用户提供一些反馈信息,可使用Invoke()方法。例如,可调用Invoke()方法运行某个代码片段以显示具有OK/Cancel按钮的对话框。如果用户单击了俺就,而且封送的代码已经完成,Invoke()方法将返回,并且可针对用户的相应执行操作

BackgroundWorker类

BackgroundWorker是.NET用于执行多线程任务的控件,它允许编程者在一个单独的线程上执行一些操作。耗时的操作(如下载和数据库事务)在不断运行时可能会导致用户界面( UI)始终处于停止响应状态。如果您需要能进行响应的用户界面,并且面临与该操作相关的连续重复,则可以使用BackgroundWorker类方便地解决问题。

如果从开始到结束只有一个异步任务在后台运行,那么使用BackgroundWorker组件是非常完美的(具有可选的进度报告和取消支持)如果还需要考虑其他事情——例如,在整个应用程序生命周期运行的异步任务,或当执行其工作时与应用程序进行通信的异步任务,就需要使用.NET的线程支持来设计自定义解决方案。

重要属性

  1. CancellationPending :只读属性,default值为false,执行CancelAsync方法后,值为true。表明应用程序请求了取消后台操作。获取一个值,指示应用程序是否已请求取消后台操作。通过在DoWork事件中判断CancellationPending属性可以认定是否需要取消后台操作(也就是结束线程);
  • IsBusy :如果后台异步操作开始执行,值为true,否则为false。获取一个值,指示 BackgroundWorker 是否正在运行异步操作。程序中使用IsBusy属性用来确定后台操作是否正在使用中;
  • WorkerReportProgress:如果BackgroundWorker支持后台操作进程更新,设置值为true,default值为false。** 获取或设置一个值,该值指示BackgroundWorker能否报告进度更新。**

重要方法

  • RunWorkerAsync() :开始执行后台操作,执行后台操作,激发DoWork事件
  • ReportProgress():激发ProgressChanged事件
  • CancelAsync() : 请求取消挂起的后台操作。提交终止后台操作的请求,并将CancellationPending属性值设为true。在程序其他地方要定时检查CancellationPending属性的值,作出相应操作,比如
if (worker.CancellationPending)
{
    e.Cancel = true;
}

重要事件

  • DoWork:调用 RunWorkerAsync 时发生,后台耗时线程可以放在这里。
  • ProgressChanged:调用 ReportProgress 时发生,UI线程放在这里,类似进度条的功能。
  • RunWorkerCompleted:当后台操作已完成、被取消或引发异常时发生,UI线程可以放在这里,任务完成后。

不要在DoWork事件处理程序中对UI线程中的对象进行操作,操作应该放在ProgressChanged和RunWorkerCompleted的事件处理程序中。

另外还有三个重要的参数是RunWorkerCompletedEventArgs以及DoWorkEventArgs、ProgressChangedEventArgs。
BackgroundWorker的各属性、方法、事件的调用机制和顺序:

整个生活周期内发生了3次重要的参数传递过程:
参数传递1:此次的参数传递是将RunWorkerAsync(Object)中的Object传递到DoWork事件的DoWorkEventArgs.Argument,由于在这里只有一个参数可以传递,所以在实际应用往封装一个类,将整个实例化的类作为RunWorkerAsync的Object传递到DoWorkEventArgs.Argument;
参数传递2:此次是将程序运行进度传递给ProgressChanged事件,实际使用中往往使用给方法和事件更新进度条或者日志信息;
参数传递3:在DoWork事件结束之前,将后台线程产生的结果数据赋给DoWorkEventArgs.Result一边在RunWorkerCompleted事件中调用RunWorkerCompletedEventArgs.Result属性取得后台线程产生的结果。
另外从上图可以看到DoWork事件是在后台线程中运行的,所以在该事件中不能够操作用户界面的内容,如需要更新用户界面,可以使用ProgressChanged事件及RunWorkCompleted事件来实现。

示例程序一

XAML

<Window x:Class="BackgroundWorkerExample.MainWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        Title="MainWindow" Height="150" Width="300">
    <StackPanel>
        <ProgressBar Name="progressBar" Height="20" Width="250" Margin="10"></ProgressBar>
        <TextBox Name="textBox" Width="50" Height="20" HorizontalAlignment="Center"></TextBox>
        <Button Name="btnProcess" Width="100" Click="btnProcess_Click" Margin="5">Start</Button>
        <Button Name="btnCancel" Width="100" Click="btnCancel_Click" Margin="5">Cancel</Button>
    </StackPanel>
</Window>

C#

namespace BackgroundWorkerExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        BackgroundWorker bgworker = new BackgroundWorker();
        public MainWindow()
        {
            InitializeComponent();

            bgworker.WorkerReportsProgress = true;
            bgworker.WorkerSupportsCancellation = true;
            bgworker.DoWork += bgworker_DoWork;
            bgworker.ProgressChanged += bgworker_ProgressChanged;
            bgworker.RunWorkerCompleted += bgworker_RunWorkerCompleted;
        }


        void bgworker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            for (int i = 1; i <= 100; i++)
            {
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                }
                else
                {
                    worker.ReportProgress(i);
                    Thread.Sleep(100);
                }
            }
        }
       
        void bgworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;
            textBox.Text = e.ProgressPercentage.ToString();
        }

        void bgworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            progressBar.Value = 0;
            if (e.Cancelled)
            {
                MessageBox.Show("Background task has been canceled", "info");
            }
            else
            {
                MessageBox.Show("Background task finished", "info");
            }
        }

        private void btnProcess_Click(object sender, RoutedEventArgs e)
        {
            if (!bgworker.IsBusy)
            {
                bgworker.RunWorkerAsync();
            }
        }

        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            bgworker.CancelAsync();
        }
    }
}

演示

img

示例程序二

public partial class MainWindow : Window
{
    BackgroundWorker worker = new BackgroundWorker();
    public MainWindow()
    {
        InitializeComponent();

        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;

        worker.DoWork += (sender,e)=> 
            {
                BackgroundWorker worker = sender as BackgroundWorker;
                string str = "";

                for (int i = 0; i < 100000000; i++)
                {
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                    }
                    else
                    {
                        if (i%5==0)
                        {
                            str = "支付成功";
                        }
                        else if (i%3==0)
                        {
                            str = "输入密码";
                        }
                        else if (i==74)
                        {
                            str = "支付失败";
                        }else
                        {
                            str = "支付中。。。";
                        }
                        worker.ReportProgress(i,str);
                        Thread.Sleep(100);
                    }
                }
               
            };
        worker.ProgressChanged += (s,e)=> 
            {
                proBar.Value = e.ProgressPercentage;
                textblock.Text = e.UserState.ToString();
                text.Text = proBar.Value.ToString();
            };
        worker.RunWorkerCompleted += (s, e) => 
            {
                proBar.Value = 0;
                if (e.Cancelled)
                {
                    MessageBox.Show("后台任务已被取消","信息");
                }
                else
                {
                    MessageBox.Show("后台任务已经完成","信息");
                }
            };
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (!worker.IsBusy)
        {
            worker.RunWorkerAsync();
        }
        
    }

    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        worker.CancelAsync();
    }
}

文章推荐

Dispatcher:https://www.cnblogs.com/chillsrc/p/4482691.html

BackgroundWorker:https://www.jianshu.com/p/b89f39c5f803

BackgroundWorker:https://www.cnblogs.com/tom-tong/archive/2012/02/22/2363965.html

登峰造极的成就源于自律
原文地址:https://www.cnblogs.com/fishpond816/p/13848275.html