为什么IProgress <t> Report(T)方法阻止UI线程?

时间:2018-07-05 20:23:43

标签: c# .net wpf asynchronous

我在下面的代码中使用Process2()方法报告进度时遇到问题。我想在读取的每一行之后增加进度条,但是这样做会阻塞UI,并且它变得无响应。如果我注释掉progress.Report()行,它将不再阻塞UI线程。有谁知道为什么会发生这种情况以及我该如何解决?

这里是可以正常工作的代码,可以粘贴到入门WPF应用程序中。

单击“运行”按钮(开始生成文件时可能会稍作停顿,等到“完成生成文件”之后),然后尝试将窗口移至另一处,该窗口将保持冻结状态。

警告:此代码将在bin \ Debug文件夹(或配置指向的任何文件)中生成一个文本文件,如果您从网络路径运行此文件,则可能无法写入此文件,因此建议从以下位置运行本地磁盘。

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WpfApplication2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Queue<string> queue = new Queue<string>();

        List<string> stringsCollection = new List<string>() { "1_abc123_A_AA_zzz", "2_abc123_AAAA_zzz", "3_abc123_AAAAAA_zzz" };

        int linesCount = 0;
        int totalLines = 0;

        string ASSEMBLY_PATH;
        string file; 
        public MainWindow()
        {
            InitializeComponent();

            ASSEMBLY_PATH = ReturnThisAssemblyPath();
            file = ASSEMBLY_PATH + @"\test.txt";
            generateFile();
        }

        private async void Button_Click2(object sender, RoutedEventArgs e)
        {
            linesCount = 0;

            Progress<int> process2_progress;

            this.progress.Value = 0;
            this.status.Text = "";

            process2_progress = new Progress<int>();
            process2_progress.ProgressChanged += Process2_progress_ProgressChanged;

            this.status.Text += "Searching..." + Environment.NewLine;
            await Task.Run(() =>
            {
                totalLines = System.IO.File.ReadLines(file).Count();

                foreach (string s in stringsCollection)
                {
                    Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() =>
                    {
                        this.status.Text += "Searching " + s + Environment.NewLine;
                    }));
                    List<string> strCollection = Process2(s, process2_progress);

                    foreach (string str in strCollection)
                        queue.Enqueue(str);
                }
            });

            this.status.Text += "DONE!!" + Environment.NewLine;
        }

        private void Process2_progress_ProgressChanged(object sender, int e)
        {
            linesCount += e;
            this.progress.Value = linesCount * 100 / totalLines;
        }

        List<string> Process2(string inputString, IProgress<int> progress)
        {
            List<string> result = new List<string>();

            foreach (string line in System.IO.File.ReadLines(file, new UTF8Encoding()))
            {
                progress.Report(1);
            }

            return result;
        }

    void generateFile()
    {
        this.status.Text += "Generating FIle..." + Environment.NewLine;
        int count = 0;
        using (StreamWriter sw = new StreamWriter(file, true))
        {
            do
            {
                sw.WriteLine(Guid.NewGuid().ToString());
                count++;
            } while (count < 51000);

        }
        this.status.Text += "Done Generating FIle!" + Environment.NewLine;
    }

        public string ReturnThisAssemblyPath()
        {
            string codeBase = Assembly.GetAssembly(typeof(MainWindow)).CodeBase;
            UriBuilder uri = new UriBuilder(codeBase);
            string path = Uri.UnescapeDataString(uri.Path);
            return System.IO.Path.GetDirectoryName(path);
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication2"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBox x:Name="status" Grid.Row="0"></TextBox>

        <Button Grid.Row="2" Height="50" Click="Button_Click2">Run2</Button>
        <ProgressBar x:Name="progress" Grid.Row="3" Height="20" ></ProgressBar>
    </Grid>
</Window>

1 个答案:

答案 0 :(得分:6)

我怀疑您的问题是您过于频繁地报告进度。如果您在<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" /> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" /> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script> <div class="container"> <div id="ReactivateAlert" style="display:none"> <div class="alert alert-danger alert-dismissible" role="alert"> When this alert box appears, the margin does not get included in the animation. </div> </div> <div class="col-md-2"> <label class="col-form-label col-md-2" for="SelectDemo">Table</label> </div> <div class="col-md-10"> <select class="form-control" id="SelectDemo" name="SelectDemo"> <option value="">--- Select a Test ---</option> <option value="Test1">Test1</option> <option value="Test2">Test2</option> <option value="Test3">Test3</option> <option value="Test4~">Test4~</option> <option value="Test5~">Test5~</option> <option value="Test6~">Test6~</option> </select> </div> </div>调用之间所做的工作是微不足道的(例如,仅从文件中读取一行),那么向UI线程分配操作将成为您的瓶颈。您的UI调度程序队列已泛滥,无法跟上新事件的发生,例如响应鼠标单击或移动。

为减轻这种情况,您应该将Report呼叫的频率降低到合理的水平-例如,仅在处理了1,000条线路时才调用它。

Report

针对评论:选择批处理大小时文件大小无关紧要。而是:为更新频率找到一个合理的目标,例如100µms。测量或估计读取和处理一条线所需的时间-例如100μs。将前者除以后者,您将得到答案。我们之所以选择1,000条,是因为我们估计1,000条线需要100µms的处理时间。最佳更新频率在10–100µms左右,这是人类感知的极限。用户不会注意到任何比这更频繁的事情。

根据以上所述,您的10行和500行文件不需要对UI发出任何更新,因为在用户有机会观察到任何进度之前,它们将在短短几毫秒内就被完全处理。 1,000,000行的文件总共需要大约100秒的时间,在此期间它将更新UI 1,000次(每100µms一次)。