Почему метод IProgress <T>Report (T) блокирует поток пользовательского интерфейса? - PullRequest
0 голосов
/ 05 июля 2018

У меня проблема с сообщением о прогрессе в методе Process2 () в моем коде ниже. Я хочу увеличивать индикатор выполнения после каждой прочитанной строки, но при этом блокируется пользовательский интерфейс, и он перестает отвечать на запросы. Если я закомментирую строку progress.Report (), он больше не блокирует поток пользовательского интерфейса. Кто-нибудь знает, почему это происходит и как я могу это исправить?

Вот полностью рабочий код, который можно вставить в стартовое 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 голосов
/ 05 июля 2018

Я подозреваю, что ваша проблема в том, что вы сообщаете о прогрессе слишком часто. Если работа, которую вы выполняете между вызовами Report, тривиальна (например, чтение только одной строки из файла), то отправка операций в поток пользовательского интерфейса станет вашим узким местом. Ваша очередь диспетчера пользовательского интерфейса затопляется, и она не поспевает за новыми событиями, такими как реагирование на щелчки или движения мыши.

Чтобы уменьшить это, вы должны уменьшить частоту ваших Report вызовов до разумного уровня - например, вызывать его только тогда, когда обрабатывается партия из 1000 строк.

int i = 0;
foreach (string line in System.IO.File.ReadLines(file, Encoding.UTF8))
{
    if (++i % 1000 == 0)
        progress.Report(1000);
}

В ответ на комментарии: Размер файла не имеет значения при выборе размера пакета. Скорее: найдите разумную цель для частоты обновления - скажем, 100 мс. Измерьте или оцените время, необходимое для чтения и обработки одной строки - например, 100 мкс. Разделите первое на второе, и вы получите ответ. Мы выбрали 1000, потому что, по нашим оценкам, для 1000 строк потребуется 100 мс. Оптимальная частота обновления составляет около 10–100 мс, что является пределом человеческого восприятия; ничего более частого, чем это, не будет замечено пользователем.

В соответствии с вышесказанным, ваши файлы из 10 и 500 строк не должны выпускать никаких обновлений в пользовательском интерфейсе, потому что они были бы обработаны полностью за считанные миллисекунды, прежде чем пользователь сможет наблюдать за любым прогрессом. Файл с 1 000 000 строк в общей сложности займет около 100 секунд, и в течение этого периода пользовательский интерфейс будет обновляться 1000 раз (один раз каждые 100 мс).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...