Dispatcher.BeginInvoke()方法使用不当导致UI界面卡死的原因分析

原文: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。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-26 12:27:36

Dispatcher.BeginInvoke()方法使用不当导致UI界面卡死的原因分析的相关文章

【C#】多线程解决UI界面卡死的问题

一个经典的例子: http://www.cnblogs.com/wangchuang/p/4485797.html 问题: 都说Invoke是同步的,BeginInvoke是异步的,但为何用BeginInvoke做耗时操作依然会卡死UI? http://www.cnblogs.com/blosaa/archive/2013/05/30/3107381.html 小结: BeginInvoke的异步是指相对于调用BeginInvoke的线程异步,而不是相对于UI线程异步.所以在UI线程调用Begi

关于windows系统DPI增大导致字体变大的原因分析

最近再学习WPF开发,其中提到一个特性“分辨率无关性”,主要功能就是实现开发的桌面程序在不同分辨率的电脑上显示时,会根据系统的DPI自动进行UI的缩放,从而不会导致应用程序的失真. 这个里面就提到了个系统DPI,这个其实在我们windows系统中(不论XP,还是vista之后的win7.win8.win10等),我们都可以设置.DPI就是指每英寸像素点数,及一英寸的长度上存在的像素数,它其实反映的是一个密度问题.windows系统默认情况下,系统DPI都是96DPI,这里还是得注意和显示器实际的

C# 串口关闭时主界面卡死原因分析

原文:C# 串口关闭时主界面卡死原因分析 问题描述 前几天用SerialPort类写一个串口的测试程序,关闭串口的时候会让界面卡死. 参考博客windows程序界面卡死的原因,得出界面卡死原因:主线程和其他的线程由于资源或者锁争夺,出现了死锁. 参考知乎文章WinForm界面假死,如何判断其卡在代码中的哪一步?,通过点击调试暂停,查看ui线程函数栈,直接定位阻塞代码的行数,确定问题出现在SerialPort类的Close()方法. 参考文章C# 串口操作系列(2) -- 入门篇,为什么我的串口程

WPF Dispatcher.BeginInvoke子线程更新UI

原文:WPF Dispatcher.BeginInvoke子线程更新UI 在开发WPF应用时出现:"调用线程无法访问此对象,因为另一个线程拥有该对象." 是因为UI线程是WPF应用的主线程,若尝试子线程更新UI线程应使用Dispatcher.BeginInvoke()或者Invoke()方法. Dispatcher.BeginInvoke() //异步执行,不等待委托结束就更新 Dispatcher.Invoke()          //代表同步执行 Action()       

关于 JavaFX ——我对通过代码写 UI界面的层级式分类方法

前言:在我的另一篇文章“我对 Java 标志符的命名方法”中有提到:对于返回控件的方法我是使用类似:W_borderPane() 的方式编写的. 现在我提出一种通过代码编写 UI 界面的层级式分类方法: 示例代码: // 根容器 private static BorderPane W_borderPaneBasis(){ // 创建 BorderPane _borderPaneBasis = new BorderPane(); // #自我定义 _borderPaneBasis.setBackg

android异步更新UI界面的方法

在android平台下,进行多线程编程时,经常需要在主线程之外的一个单独的线程中进行某些处理,然后更新用户界面显示.但是,在主线线程之外的线程中直接更新页面显示的问题是:系统会报这个异常,android.view.viewroot$calledfromwrongthreadexception: only the original thread that created a view hierarchy can touch its views. (只有原始创建这个视图层次(view hierach

一种WPF在后台线程更新UI界面的简便方法

WPF框架规定只有UI线程(主线程)可以更新界面,所有其他后台线程无法直接更新界面.幸好,WPF提供的SynchronizationContext类以及C#的Lambda表达式提供了一种方便的解决方法.以下是代码: public static SynchronizationContext s_SC = Synchronization.Current; //主窗口类的静态成员 在App类中: static Thread s_MainThread = Thread.CurrentThread; //

Unity中UI界面颤抖解决方法

将Render Mode中属性改为Screen Space - Camera 摄像机挂在Canvas属性下会出现UI界面颤抖的效果. UI界面颤抖解决方式:将Render Mode中属性改为Screen Space - Overlay,如下图所示:

c# winform编程之多线程ui界面资源修改总结篇

单线程的winfom程序中,设置一个控件的值是很easy的事情,直接 this.TextBox1.value = "Hello World!";就搞定了,但是如果在一个新线程中这么做,比如: private void btnSet_Click(object sender, EventArgs e) {        Thread t = new Thread(new ParameterizedThreadStart(SetTextBoxValue));     //当然也可以用匿名委托