Невозможно обновить индикатор выполнения в GUI (MainWindow.xaml) с другой страницы User.cs, запущенной в другом потоке с использованием c# - PullRequest
0 голосов
/ 22 апреля 2020

У меня есть GUI MainWindow.xaml , где у меня есть ProgressBar , который должен отображать ход огромной операции сравнения, записывается лог c операции на странице User.cs .
По сути, MainWindow.xaml.cs вызывает метод compare () класса User и выполняет сравнение и отображая вывод в конце.

Я использовал индикатор занятости во время сравнения, которое зависает и показывает пользовательское сообщение в конце.

MainWindow. xaml.cs

при нажатии кнопки «Выполнить»:

{
    User compare = new User(Parser1, Parser2);
    busyIndicator.IsBusy = true;

    ThreadStart job = new ThreadStart(() =>
    {
        compare.compare();
        //above line calls a method "compare" of User.cs class and does the comparison and returns back to this page

        Dispatcher.Invoke(DispatcherPriority.Normal, new Action( () =>
        {
            busyIndicator.IsBusy = false;
            MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show("Finished", "Generation Finished", MessageBoxButton.OK);
        } ) );
    });

    Thread thread = new Thread(job);
    thread.Start();
}

User.cs

class User
{
    public void compare()
    {
        for( i=0, i < 100, i++ )
        {
            int val = i; 
        }
    }
}

так как мы можем покажите прогресс compare.compare() на индикаторе прогресса в GUI как процент выполнения.

Я использовал BackGroundWorker, но не смог получить прогресс compare.compare()

Спасибо,

1 Ответ

2 голосов
/ 22 апреля 2020

Часть 1: Потоки

Не следует использовать new Thread:

  • Потоки стоят дорого в Microsoft Windows (в отличие от Linux, где потоки обычно дешевы ).
  • Используйте пул потоков, который содержит потоки, которые уже созданы. NET и ожидают работы.
  • Использование Task.Run запустит задание в пуле потоков поток по умолчанию и представляет задание как Task объект, который вы можете безопасно await, потому что диспетчер WPF поддерживает планировщик Task и всегда будет возобновлять работу в потоке пользовательского интерфейса после await.
  • Обратите внимание, что даже если это означает использование ключевых слов async и await, этот код на самом деле не является «настоящим» асинхронным кодом (в отличие от реального asyn c IO для сетевых сокетов и файловой системы) - это просто. NET представляет одновременно выполняющийся фоновый поток как асинхронную операцию.
  • При использовании await в контексте WPF (или WinForms) важно, чтобы никогда звоните .ConfigureAwait(false), если вы не теперь безопасно возобновить работу в потоке без пользовательского интерфейса. (Но можно использовать .ConfigureAwait(true)).

Это также делает ваш код на намного проще . Вот все, что вам нужно:

private async void Run_Clicked( Object sender, EventArgs e )
{
    User compare = new User( this.Parser1, this.Parser2 );
    busyIndicator.IsBusy = true;

    await Task.Run( () => compare.compare() );

    busyIndicator.IsBusy = false;
    MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show( "Finished", "Generation Finished", MessageBoxButton.OK );
} 

Тем не менее, поскольку await перезапустит любые исключения, вы всегда должны восстанавливать состояние формы внутри блока finally:

private async void Run_Clicked( Object sender, EventArgs e )
{
    User compare = new User( this.Parser1, this.Parser2 );
    busyIndicator.IsBusy = true;

    try
    {
        await Task.Run( () => compare.compare() );

        MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show( "Finished", "Generation Finished", MessageBoxButton.OK );
    }
    finally
    {
        busyIndicator.IsBusy = false;
    }
} 

Часть 2. Отчет о ходе выполнения

Чтобы указать информацию о ходе выполнения для вызывающего абонента, используйте IProgress<T>. Аргументом типа IProgress<T> должно быть неизменяемое значение или объект (т. Е. Тип значения, например int или float, неизменяемый ссылочный тип, например string, ValueTuple или пользовательский объект класса с только readonly полями).

  • Я предполагаю, что вы хотите сообщить только процентное значение, поэтому мы будем используйте float (он же Single) для T (со значениями 0-1.0, представляющими 0-100%.
  • . Я не рекомендую использовать int для представления процентов, поскольку это означает " разрешение "индикатора выполнения будет слишком низким (всего 100 шагов), а не плавно прогрессирует от 0 до 100% с десятичными знаками, например, 0,1%, 55,55%, 99,95% и т. д. c.
  • Если вы хотите вернуть String сообщение с процентом, затем используйте ValueTuple: IProgress<(String message, float percentage)>.

Сначала измените метод compare (который должен называться Compare, кстати) принять IProgress<float> и сообщить о прогрессе, используя progress?.Report( value ) (используйте оператор ?., чтобы разрешить вызывающим абонентам отказаться от предоставления IProgress объекта): * 108 4 *

class User
{
    public void Compare( IProgress<float> progress = null )
    {
        for( Int32 i = 0; i < 100; i++ )
        {
            progress?.Report( i / 100f );
        } 

        progress?.Report( 1f ); // 100% completion
    }
}

В вашем коде WPF мы будем использовать реализацию по умолчанию IProgress<T> in. NET, называемую Progress<T> , которая обеспечивает встроенную синхронизацию потока пользовательского интерфейса :

Любой обработчик, предоставленный конструктору или обработчикам событий, зарегистрированным в событии ProgressChanged, вызывается через экземпляр SynchronizationContext, захваченный при его создании.

Примерно так:

class MyWpfWindow
{
    private void UpdateProgressBar( float value )
    {
        this.progressBar.Value = value * 100; // Assuming ProgressBar's scale is 0-100.
    }

    private async void Run_Clicked( Object sender, EventArgs e )
    {
        User compare = new User( this.Parser1, this.Parser2 );
        busyIndicator.IsBusy = true;

        Progress<float> progress = new Progress<float>( this.UpdateProgressBar );

        try
        {
            await Task.Run( () => compare.Compare( progress ) );

            MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show( "Finished", "Generation Finished", MessageBoxButton.OK );
        }
        finally
        {
            busyIndicator.IsBusy = false;
        }
    }
}
...