I have created a Winforms App that uses the Open Hardware Monitor to display PC temps in a gauge format using Live Charts. I am aware that the following code causes the UI to become unresponsive as the charts are updated but I am not able to figure out how to implement any form of threading to work with this or how to change how I have coded the application to keep the UI responsive. The timer will tick every two seconds which fetches the values and updates the gauges.
References added to the project are:
See my code below, any advice would be appreciated.
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)
{
}
}
}
}
My suggestion is to separate the data retrieval from the presentation. This way you will be able to offload the heavy work of data retrieval to a background thread, without having to worry how you will update the UI controls at the same time. Here is how you could make a GetSensorData
method that fetches the data as an array of ValueTuple<string, double>
elements, representing the name and the value of each sensor:
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();
}
Then you could use the Task.Run
method to offload the heavy work of retrieving the data to a background thread (a ThreadPool
thread). This method returns a Task
, that could be awaited asynchronously so that the code below the await
has available data to work with.
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);
}
}
}
Finally you'll have to make the event handlers 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 { }
}