Диспетчер задач WPF: обработка обновления значения загрузки ЦП?

В настоящее время я несколько новичок в С#/wpf (и кодировании в целом). Я решил начать еще один проект, будучи своего рода «менеджером задач».

(Хотя я использую привязку, это НЕ проект MVVM, поэтому все ответы приветствуются)

Если вы когда-либо открывали диспетчер задач, вы знаете, что одним из основных полезных инструментов, который он предоставляет, является обновление представления об использовании ЦП / ОЗУ / чего бы то ни было. Сообщение пользователю, какой процент ресурса он использует.

Моя проблема заключается в том, что я не получаю процент ЦП. Я не уверен, как эффективно обновить свойство text для загрузки ЦП в пользовательском интерфейсе.

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

В настоящее время код настроен следующим образом:

  1. Когда страница загружается, общедоступный BgWrk создает новый экземпляр самого себя.
  2. Добавляет задачу, которая будет вызываться при запуске.
  3. BgWrk запущен.
  4. Создан новый экземпляр вызываемого метода.
  5. Диспетчер вызывается в основном потоке для обновления пользовательского интерфейса.
  6. Invoke состоит из установки общедоступной строки PerCpu (привязанной к другому классу, используя INotifyPropertyChanged и все) для возвращаемого значения CpuPerUsed «граббера».
  7. BgWrk утилизирован.
  8. Циклы программы (это, скорее всего, проблема).

    private void Grid_Loaded(object sender, RoutedEventArgs e)
    {
    
        BgWrk = new BackgroundWorker();
        BgWrk.DoWork += new DoWorkEventHandler(BackgroundWorker1_DoWork);
        BgWrk.RunWorkerAsync(); 
    
    }
    
    private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
    
        while (true)
        {
            CpuInfoGrabber grabber = new CpuInfoGrabber();
            Application.Current.Dispatcher.Invoke(new Action (() => Bnd.PerCpu = grabber.CpuPerUsed()));
            BgWrk.Dispose();
        }
    
    }
    

Опять же, код работает, но он ОЧЕНЬ медленный из-за загрузки всех этих данных. Любые предложения о том, как сделать эту работу хорошо, приветствуются!

Спасибо


person Aaron    schedule 13.07.2018    source источник
comment
Я бы, вероятно, использовал таймер для опроса использования процессора. Вы можете использовать Timers.Timer, который вызывает свой обработчик в другом потоке. В обработчике вы можете вернуться к потоку графического интерфейса и обновить свое свойство.   -  person WBuck    schedule 13.07.2018
comment
Кроме того, убедитесь, что вы делаете свои тяжелые вещи вне метода делегата Action. Я предполагаю, что grabber.CpuPerUsed() - это то, что вы хотите запустить в фоновом потоке. С вашей текущей настройкой вы запускаете этот поток графического интерфейса.   -  person WBuck    schedule 13.07.2018


Ответы (2)


Вместо цикла вы можете использовать таймер для периодического опроса использования ЦП.

class Test
{
    private System.Timers.Timer _timer;

    public Test( )
    {
        _timer = new System.Timers.Timer
        {
            // Interval set to 1 millisecond.
            Interval = 1,
            AutoReset = true,                
        };
        _timer.Elapsed += _timer_Elapsed;
        _timer.Enabled = true;
        _timer.Start( );
    }

    private void _timer_Elapsed( object sender, System.Timers.ElapsedEventArgs e )
    {
        // This handler is not executed on the gui thread so
        // you'll have to marshal the call to the gui thread
        // and then update your property.
       var grabber = new CpuInfoGrabber();
       var data = grabber.CpuPerUsed();
       Application.Current.Dispatcher.Invoke( ( ) => Bnd.PerCpu = data );
    }
}
person WBuck    schedule 13.07.2018
comment
Я использовал оба этих ответа, чтобы решить проблему. Надо было просто уделять больше внимания отладке. Проблема заключалась в том, что, несмотря на то, что я использовал фоновые рабочие процессы, я совершил глупую ошибку, поместив вызов метода в текущий диспетчер, из-за чего пользовательский интерфейс работал ОЧЕНЬ медленно. - person Aaron; 13.07.2018
comment
Я бы рекомендовал использовать DispatchTimer вместо ThreadingTimer в WPF, когда это возможно. Это означает, что вам не нужно иметь дело с межпотоковыми вызовами через Dispatcher, вы просто напрямую обновляете свойства. - person Bradley Uffner; 13.07.2018
comment
Придется выкладывать свой код, но лично я Dispatch Timer не пользовался. Единственным Dispatcher, который я использовал, был сам Dispatcher (Current.Dispatcher......). Честно говоря, вам не нужен таймер, если у вас есть правильная логика для потоков. Многопоточность распределяет процесс достаточно, чтобы я мог обновлять текущее использование в среднем примерно 3-4 раза (работая на ЧРЕЗВЫЧАЙНО медленном компьютере, кто знает, как долго). Поэтому до тех пор, пока вы не основываете свои вызовы методов на потоке пользовательского интерфейса, у вас, вероятно, все будет хорошо. - person Aaron; 13.07.2018
comment
Хорошая вещь с подходом таймера заключается в том, что вы можете легко контролировать разрешение, не прибегая к таким вещам, как Thread.Sleep, если вы работаете в цикле в фоновом потоке. На самом деле вам, вероятно, не нужно так часто опрашивать процессор (может быть, от полсекунды до секунды). - person WBuck; 14.07.2018

Я бы использовал Task.Run вместо BackgroundWorker в вашем случае:

private void Grid_Loaded(object sender, RoutedEventArgs e)
{
    //Keep it running for 5 minutes
    CancellationTokenSource cts = new CancellationTokenSource(new TimeSpan(hours: 0, minutes: 5, seconds: 0));

    //Keep it running until user closes the app
    //CancellationTokenSource cts = new CancellationTokenSource();

    //Go to a different thread
    Task.Run(() =>
    {
        //Some dummy variable
        long millisecondsSlept = 0;

        //Make sure cancellation not requested
        while (!cts.Token.IsCancellationRequested)
        {
            //Some heavy operation here
            Thread.Sleep(500);
            millisecondsSlept += 500;

            //Update UI with the results of the heavy operation
            Application.Current.Dispatcher.Invoke(() => txtCpu.Text = millisecondsSlept.ToString());
        }
    }, cts.Token);
}
person Aly Elhaddad    schedule 13.07.2018
comment
Вероятно, в этом сценарии имеет смысл пометить метод делегата Task.Run как асинхронный, а затем использовать await Task.Delay( 500 ) вместо Thread.Sleep, чтобы не тратить впустую отличный поток, заставляя его ждать. Если, конечно, вы не использовали это для дросселирования, а для имитации работы. - person WBuck; 13.07.2018
comment
Я так понимаю - я просто имитирую тяжелую операцию. - person Aly Elhaddad; 13.07.2018