zoukankan      html  css  js  c++  java
  • 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

    登峰造极的成就源于自律
  • 相关阅读:
    美国贷款买饭的房屋保险
    ArrayList和数组间的相互转换
    JList动态添加元素
    美国交往礼仪
    刘元普双生贵子(但行好事,莫问前程)
    CountDownLatch与CyclicBarrier
    彻底理解Java的feature模式
    Java中的Future模式原理自定义实现
    浅谈Java Future接口
    Future接口和Callable接口以及FeatureTask详解
  • 原文地址:https://www.cnblogs.com/fishpond816/p/13848275.html
Copyright © 2011-2022 走看看