• 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

    登峰造极的成就源于自律
  • 相关阅读:
    正则表达式点滴
    异步处理与界面交互
    关于利用VS2008创建项目遇到的小困惑备忘
    using App.cofig to Store value
    Castle ActiveRecord学习笔记三:初始化配置
    无服务器端的UDP群聊功能剖析
    为VS2010默认模板添加版权信息
    理论有何用?不问“何用”,先问“用否”!
    微软没有公开的游标分页
    那些满脑子只考虑后台数据库的人他整天研究的就是针对自己查询一些数据的sql语句
  • 原文地址:https://www.cnblogs.com/fishpond816/p/13848275.html
Copyright © 2020-2023  润新知