zoukankan      html  css  js  c++  java
  • Dispatcher.BeginInvoke()方法使用不当导致UI界面卡死的原因分析

    前段时间,公司同事开发了一个小工具,在工具执行过程中, UI 界面一直处于卡死状态。

    通过阅读代码发现,主要是由于 Dispatcher.BeginInvoke()方法使用不当导致的。

    本文将通过一个WPF模拟程序来演示一下界面卡死的现象,并通过修改代码来解决界面卡死的问题。

    希望通过对本文的学习,大家能对Dispatcher.BeginInvoke()方法有一个新的认识。

    文章开篇直接给出界面卡死的示例代码。

    示例WPF 程序,用来计算 1~n 的和值,这里的 n 可以是 1 亿 ~25  亿之间的某个值,通过界面录入,结果显示在 n 输入框后面的文本框中,既然是 WPF 程序,代码包含 xaml cs 代码两部分,本文一并给出。

    以下为cs代码:

    using System;
    using System.Windows;
    using System.Threading;
    
    namespace DispatcherExample
    {
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                Int64 inputNumber;
                if (!Int64.TryParse(this.textBox1.Text, out inputNumber))
                {
                    MessageBox.Show("请输入1亿-10亿皑间的整型数据!");
                    return;
                }
                if (inputNumber > 2500000000 || inputNumber<100000000)
                {
                    MessageBox.Show("请输入1亿-10亿间的整型数据!");
                    return;
                }
                Thread newThread = new Thread(new ParameterizedThreadStart(GetResult));
                newThread.Start(inputNumber);
            }
    
            private void GetResult(object inputNumber)
            {
                this.Dispatcher.BeginInvoke((Action)delegate()
                {
                    this.textBox2.Text = CalcSum((Int64)inputNumber).ToString();
                });
            }
    
            private double CalcSum(Int64 inputNumber)
            {
                double sum=0;
                for (int i = 0; i < inputNumber; i++)
                {
                    sum +=i;
                }
                return sum;
            }
        }
    }

    以下为xaml代码:

    <Window x:Class="DispatcherExample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="求(和)你亿万次~~" Height="350" Width="525" ResizeMode="NoResize">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="252*" />
                <ColumnDefinition Width="251*" />
            </Grid.ColumnDefinitions>
            <Button Content="计算和值" Height="23" HorizontalAlignment="Left" Margin="213,168,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" Grid.ColumnSpan="2" />
            <Label Content="输入1亿-25亿间的数字:" Height="28" HorizontalAlignment="Left" Margin="36,93,0,0" Name="label1" VerticalAlignment="Top" />
            <TextBox Height="23" HorizontalAlignment="Left" Margin="158,96,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Grid.ColumnSpan="2" />
            <TextBox Height="23" HorizontalAlignment="Left" Margin="35,96,0,0" Name="textBox2" VerticalAlignment="Top" Width="177" Text="结果看这里..." Grid.Column="1" />
        </Grid>
    </Window>

    执行程序,界面如下:

    输入2500000000 ,点击“计算和值”按钮,程序开始计算和值,界面卡死,无法再操作该程序(如移动位置或重新输入等)。

    分析代码,发现问题应该出在下面的代码中,因为该部分代码中存在调用 UI 主线程的操作,此种操作不当往往会导致界面卡死的现象。

    private void GetResult(object inputNumber)
    {
         this.Dispatcher.BeginInvoke((Action)delegate()
         {
               this.textBox2.Text = CalcSum((Int64)inputNumber).ToString();
         });
    }

    那么,问题到底出在哪里呢?

    要想弄清楚这点,还得了解一下Dispatcher.BeginInvoke()方法。

    MSDN 上对 Dispatcher.BeginInvoke方法的解释如下

    Dispatcher . BeginInvoke 方法 (Action)

    在与  Dispatcher 关联的线程上异步执行指定的委托。  

    那么本实例中,与  Dispatcher 关联的线程 是什么呢?

    要想弄清楚这点很简单。只要知道 this .Dispatcher.BeginInvoke()中的this指的是什么就可以了。在Visual studio中将鼠标至于this上,发现this指的是当前的窗体类(如下图),即程序的主线程。

    到这,我们应该知道问题出在哪里了。

    原因是:在GetResult()方法中,将求和的操作交由主线程来完成,当计算未完成时,界面自然会被卡死。

    通过与同事交谈了解到,他其实想要的是:新开一个线程来完成自己预想的运算(类似于示例程序中的求和运算),在结果出来后再调用主线程显示结果。

    这样界面就不会出现卡死现象,但是上面的代码并没有达到预想结果。

    原因前面已经交代了,因为这段代码将求和的计算仍然丢给了主线程,尽管新开了线程,但是新开线程并不进行求和运算,可以说是绕了一圈又回来了。

    主线程开新线程,新线程又调用主线程。这有点像工作中的踢皮球,我给你一件事,你说不会,又踢回给我。

    找到原因再修改就简单了,修改后的代码如下:

    private void GetResult(object inputNumber)
    {
         double result=CalcSum((Int64)inputNumber);
         this.Dispatcher.BeginInvoke((Action)delegate()
         {
               //this.textBox2.Text = CalcSum((Int64)inputNumber).ToString();
               this.textBox2.Text = result.ToString();          
         });
    }

    至于为什么要这样修改,我想:你懂的。

    再次执行程序,输入 2500000000 ,求和,界面不再存在卡死现象。

    就扯到这里了,我要米西米西了, 88

  • 相关阅读:
    java jni 调用c语言函数
    BeautifulSoup入门
    Python单引号、双引号、三个双引号的区别
    Request库的安装与使用
    awk命令入门
    sed命令入门
    编译生成protobuf的jar包
    编辑crontab添加Linux计划任务
    tar命令详解及使用实例
    MySQL用户管理
  • 原文地址:https://www.cnblogs.com/xuxiaoshuan/p/6496546.html
Copyright © 2011-2022 走看看