Сохранение UI отзывчивым с foreach l oop в WinForms - PullRequest
0 голосов
/ 26 апреля 2020

Я создал приложение Winforms, которое использует Open Hardware Monitor для отображения температуры P C в формате манометра с использованием текущих графиков. Мне известно, что следующий код приводит к тому, что пользовательский интерфейс перестает отвечать на запросы при обновлении диаграмм, но я не могу понять, как реализовать какую-либо форму многопоточности для работы с этим или как изменить способ кодирования приложения, чтобы сохранить Пользовательский интерфейс отзывчивый. Таймер будет срабатывать каждые две секунды, выбирая значения и обновляя датчики.

Ссылки, добавленные в проект:

  • OpenHardwareMonitorLib
  • LiveCharts.Winforms + Dependencies

См. Мой код ниже, любые советы будут оценены.

UserControls.TempGauge

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Media;

namespace PCDashboard.UserControls
{
public partial class TempGauge : UserControl
{
    public TempGauge(string name)
    {
        try
        {
            //Initialize
            InitializeComponent();

            //Gauge Properties
            this.Name = name;
            gaugeName.Text = name;
            gauge.Uses360Mode = true;
            gauge.From = 0;
            gauge.To = 110;
            gauge.Value = 0;
            gauge.HighFontSize = 60;
            gauge.Base.Foreground = System.Windows.Media.Brushes.White;
            gauge.InnerRadius = 0;
            gauge.GaugeBackground = Classes.Colours.Blue;
        }
        catch (Exception)
        {

        }
    }
  }
}

MainForm

using OpenHardwareMonitor.Hardware;
using PCDashboard.Classes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace PCDashboard.Forms
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            try
            {
                GetSystemTemps(false);
                timer.Enabled = true;
            }
            catch (Exception)
            {
            }
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void GetSystemTemps(bool UpdateGauge)
        {
            try
            {
                UpdateVisitor updateVisitor = new UpdateVisitor();
                Computer computer = new Computer();
                computer.Open();

                //Which components to read?
                computer.CPUEnabled = true;
                computer.GPUEnabled = true;
                computer.MainboardEnabled = true;
                computer.RAMEnabled = true;

                computer.Accept(updateVisitor);

                foreach (IHardware hardware in computer.Hardware)
                {
                    foreach (ISensor sensor in hardware.Sensors.Where(
                        x => x.SensorType == SensorType.Temperature))
                    {
                        if (UpdateGauge)
                        {
                            UserControls.TempGauge tempGauge =
                                ((UserControls.TempGauge)gaugeContainer
                                    .Controls[sensor.Name]);

                            if (tempGauge != null)
                            {
                                tempGauge.gauge.Value = Math.Round(
                                    double.Parse(sensor.Value.ToString()), 0);
                            }
                        }
                        else
                        {
                            UserControls.TempGauge tempGauge =
                                new UserControls.TempGauge(sensor.Name);
                            gaugeContainer.Controls.Add(tempGauge);
                        }
                    }
                }

                computer.Close();
            }
            catch (Exception)
            {
            }
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            try
            {
                GetSystemTemps(true);
            }
            catch (Exception)
            {
            }
        }
    }
}

enter image description here

Ответы [ 2 ]

1 голос
/ 26 апреля 2020

Использование таймера для этого вполне разумно. Но есть три вещи, которые вы можете сделать, чтобы ваше приложение оставалось отзывчивым.

Во-первых, я бы проверил свойство Interval в таймере. Все, что меньше 100, вероятно, слишком мало, и вы можете, вероятно, go немного выше. Даже 250 по-прежнему 4 раза в секунду, а 400 - более двух раз в секунду.

Во-вторых, я бы заключил в скобку l oop с вызовами SuspendLayout() и ResumeLayout(). Это поможет вашей форме более эффективно справляться с перерисовкой в ​​пакете.

Наконец, IIR C вы можете установить для свойства Enabled таймера значение false для событий формы Move и вернуться к значению true снова для событий ResizeEnd, чтобы отключить обновления, когда кто-то перемещает форму. Это для меня еще очень давно, так что вы захотите проверить это.

1 голос
/ 26 апреля 2020

Я предлагаю отделить получение данных от презентации. Таким образом, вы сможете перенести тяжелую работу по извлечению данных в фоновый поток, не беспокоясь о том, как вы будете одновременно обновлять элементы управления пользовательским интерфейсом. Вот как можно создать метод GetSensorData, который извлекает данные в виде массива элементов ValueTuple<string, double>, представляющих имя и значение каждого датчика:

private (string, double)[] GetSensorData()
{
    var list = new List<(string, double)>();
    Computer computer = new Computer();
    computer.Open();
    //...
    foreach (IHardware hardware in computer.Hardware)
    {
        foreach (ISensor sensor in hardware.Sensors.Where(
            x => x.SensorType == SensorType.Temperature))
        {
            list.Add(sensor.Name, double.Parse(sensor.Value.ToString()));
        }
    }
    computer.Close();
    return list.ToArray();
}

Тогда Вы можете использовать метод Task.Run, чтобы переложить тяжелую работу по извлечению данных в фоновый поток (поток ThreadPool). Этот метод возвращает Task, который можно ожидать асинхронно, так что код под await имеет доступные данные для работы.

private async Task UpdateSystemTempsAsync(bool updateGauge)
{
    var data = await Task.Run(() => GetSensorData()); // Offload to thread-pool

    // While awaiting the UI remains responsive

    // After the await we are back in the UI thread

    foreach (var (sensorName, sensorValue) in data)
    {
        if (updateGauge)
        {
            UserControls.TempGauge tempGauge =
                ((UserControls.TempGauge)gaugeContainer.Controls[sensorName]);
            if (tempGauge != null)
            {
                tempGauge.gauge.Value = Math.Round(sensorValue, 0);
            }
        }
        else
        {
            var tempGauge = new UserControls.TempGauge(sensorName);
            gaugeContainer.Controls.Add(tempGauge);
        }
    }
}

Наконец, вам нужно сделать обработчики событий async:

private async void MainForm_Load(object sender, EventArgs e)
{
    try
    {
        await UpdateSystemTempsAsync(false);
        timer.Enabled = true;
    }
    catch { }
}

private async void timer_Tick(object sender, EventArgs e)
{
    timer.Enabled = false;
    try
    {
        await UpdateSystemTempsAsync(true);
        timer.Enabled = true;
    }
    catch { }
}
...